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