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