Renaming openecomp to onap
[aai/search-data-service.git] / src / main / java / org / onap / aai / 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.onap.aai.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
29 import org.onap.aai.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreDataEntityImpl;
30 import org.onap.aai.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreInterface;
31 import org.onap.aai.sa.searchdbabstraction.entity.AggregationResults;
32 import org.onap.aai.sa.searchdbabstraction.entity.DocumentOperationResult;
33 import org.onap.aai.sa.searchdbabstraction.entity.SearchOperationResult;
34 import org.onap.aai.sa.searchdbabstraction.logging.SearchDbMsgs;
35 import org.onap.aai.sa.searchdbabstraction.searchapi.SearchStatement;
36 import org.openecomp.cl.api.LogFields;
37 import org.openecomp.cl.api.LogLine;
38 import org.openecomp.cl.api.Logger;
39 import org.openecomp.cl.eelf.LoggerFactory;
40
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43 import javax.ws.rs.core.HttpHeaders;
44 import javax.ws.rs.core.MediaType;
45 import javax.ws.rs.core.Response;
46 import javax.ws.rs.core.Response.Status;
47
48 public class DocumentApi {
49   private static final String REQUEST_HEADER_RESOURCE_VERSION = "If-Match";
50   private static final String RESPONSE_HEADER_RESOURCE_VERSION = "ETag";
51   private static final String REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION = "X-CreateIndex";
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, implicitlyCreateIndex(headers));
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, implicitlyCreateIndex(headers));
161       } else {
162         result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
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   
467   /**
468    * Checks the supplied HTTP headers to see if we should allow the underlying document 
469    * store to implicitly create the index referenced in a document PUT or POST if it
470    * does not already exist in the data store.
471    * 
472    * @param headers - The HTTP headers to examine.
473    * 
474    * @return - true if the headers indicate that missing indices should be implicitly created,
475    *           false otherwise.
476    */
477   private boolean implicitlyCreateIndex(HttpHeaders headers) {
478     
479     boolean createIndexIfNotPresent = false;
480     String implicitIndexCreationHeader = 
481         headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
482     
483     if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
484       createIndexIfNotPresent = true;
485     }
486     
487     return createIndexIfNotPresent;
488   }
489   
490   
491   private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
492       throws JsonProcessingException {
493     StringBuffer output = new StringBuffer();
494     output.append("{\r\n\"searchResult\":");
495     output.append(mapper.writerWithDefaultPrettyPrinter()
496         .writeValueAsString(result.getSearchResult()));
497     AggregationResults aggs = result.getAggregationResult();
498     if (aggs != null) {
499       output.append(",\r\n\"aggregationResult\":");
500       output.append(mapper.setSerializationInclusion(Include.NON_NULL)
501           .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
502     }
503     output.append("\r\n}");
504     return output.toString();
505   }
506
507   private Response handleError(HttpServletRequest request, String message, Status status) {
508     logResult(request, status);
509     return Response.status(status).entity(message).type(MediaType.APPLICATION_JSON).build();
510   }
511
512   void logResult(HttpServletRequest request, Response.Status status) {
513
514     logger.info(SearchDbMsgs.PROCESS_REST_REQUEST, (request != null) ? request.getMethod() : "",
515         (request != null) ? request.getRequestURL().toString() : "",
516         (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
517
518     auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
519         new LogFields()
520             .setField(LogLine.DefinedFields.RESPONSE_CODE, status.getStatusCode())
521             .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
522         (request != null) ? request.getMethod() : "",
523         (request != null) ? request.getRequestURL().toString() : "",
524         (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
525
526     // Clear the MDC context so that no other transaction inadvertently
527     // uses our transaction id.
528     ApiUtils.clearMdcContext();
529   }
530 }