2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.sa.rest;
23 import com.fasterxml.jackson.annotation.JsonInclude.Include;
24 import com.fasterxml.jackson.core.JsonProcessingException;
25 import com.fasterxml.jackson.databind.ObjectMapper;
26 import org.onap.aai.cl.api.LogFields;
27 import org.onap.aai.cl.api.LogLine;
28 import org.onap.aai.cl.api.Logger;
29 import org.onap.aai.cl.eelf.LoggerFactory;
30 import org.onap.aai.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreDataEntityImpl;
31 import org.onap.aai.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreInterface;
32 import org.onap.aai.sa.searchdbabstraction.entity.AggregationResults;
33 import org.onap.aai.sa.searchdbabstraction.entity.DocumentOperationResult;
34 import org.onap.aai.sa.searchdbabstraction.entity.SearchOperationResult;
35 import org.onap.aai.sa.searchdbabstraction.logging.SearchDbMsgs;
36 import org.onap.aai.sa.searchdbabstraction.searchapi.SearchStatement;
37 import org.onap.aai.sa.searchdbabstraction.searchapi.SuggestionStatement;
38 import org.springframework.http.HttpHeaders;
39 import org.springframework.http.HttpStatus;
40 import org.springframework.http.MediaType;
41 import org.springframework.http.ResponseEntity;
43 import javax.servlet.http.HttpServletRequest;
44 import javax.servlet.http.HttpServletResponse;
45 //import javax.ws.rs.core.HttpHeaders;
46 //import javax.ws.rs.core.MediaType;
47 //import javax.ws.rs.core.Response;
48 //import javax.ws.rs.core.Response.Status;
51 public class DocumentApi {
52 private static final String REQUEST_HEADER_RESOURCE_VERSION = "If-Match";
53 private static final String RESPONSE_HEADER_RESOURCE_VERSION = "ETag";
54 private static final String REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION = "X-CreateIndex";
56 protected SearchServiceApi searchService = null;
58 private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
59 private Logger auditLogger = LoggerFactory.getInstance()
60 .getAuditLogger(DocumentApi.class.getName());
62 public DocumentApi(SearchServiceApi searchService) {
63 this.searchService = searchService;
66 public ResponseEntity<String> processPost(String content, HttpServletRequest request, HttpHeaders headers,
67 HttpServletResponse httpResponse, String index,
68 DocumentStoreInterface documentStore) {
70 // Initialize the MDC Context for logging purposes.
71 ApiUtils.initMdcContext(request, headers);
74 ObjectMapper mapper = new ObjectMapper();
75 mapper.setSerializationInclusion(Include.NON_EMPTY);
76 if (content == null) {
77 return handleError(request, content, HttpStatus.BAD_REQUEST);
82 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
83 ApiUtils.SEARCH_AUTH_POLICY_NAME);
84 } catch (Exception e) {
85 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
86 "DocumentApi.processPost",
88 return handleError(request, content, HttpStatus.FORBIDDEN);
92 return handleError(request, content, HttpStatus.FORBIDDEN);
95 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
96 document.setContent(content);
98 DocumentOperationResult result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
100 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
101 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
103 output = result.getError() != null
104 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
105 : result.getFailureCause();
108 if (httpResponse != null) {
109 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
111 ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
112 logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
113 logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
115 // Clear the MDC context so that no other transaction inadvertently
116 // uses our transaction id.
117 ApiUtils.clearMdcContext();
120 } catch (Exception e) {
121 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
125 public ResponseEntity<String> processPut(String content, HttpServletRequest request, HttpHeaders headers,
126 HttpServletResponse httpResponse, String index,
127 String id, DocumentStoreInterface documentStore) {
129 // Initialize the MDC Context for logging purposes.
130 ApiUtils.initMdcContext(request, headers);
133 ObjectMapper mapper = new ObjectMapper();
134 mapper.setSerializationInclusion(Include.NON_EMPTY);
135 if (content == null) {
136 return handleError(request, content, HttpStatus.BAD_REQUEST);
141 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.PUT,
142 ApiUtils.SEARCH_AUTH_POLICY_NAME);
143 } catch (Exception e) {
144 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
145 "DocumentApi.processPut",
147 return handleError(request, content, HttpStatus.FORBIDDEN);
151 return handleError(request, content, HttpStatus.FORBIDDEN);
154 String resourceVersion = headers.getFirst(REQUEST_HEADER_RESOURCE_VERSION);
156 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
158 document.setContent(content);
159 document.setVersion(resourceVersion);
161 DocumentOperationResult result = null;
162 if (resourceVersion == null) {
163 result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
165 result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
168 String output = null;
169 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
170 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
172 output = result.getError() != null
173 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
174 : result.getFailureCause();
176 if (httpResponse != null) {
177 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
179 ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
180 logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
182 // Clear the MDC context so that no other transaction inadvertently
183 // uses our transaction id.
184 ApiUtils.clearMdcContext();
187 } catch (Exception e) {
188 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
192 public ResponseEntity<String> processDelete(String content, HttpServletRequest request, HttpHeaders headers,
193 HttpServletResponse httpResponse, String index, String id,
194 DocumentStoreInterface documentStore) {
196 // Initialize the MDC Context for logging purposes.
197 ApiUtils.initMdcContext(request, headers);
200 ObjectMapper mapper = new ObjectMapper();
201 mapper.setSerializationInclusion(Include.NON_EMPTY);
204 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.DELETE,
205 ApiUtils.SEARCH_AUTH_POLICY_NAME);
206 } catch (Exception e) {
207 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
208 "DocumentApi.processDelete",
210 return handleError(request, content, HttpStatus.FORBIDDEN);
214 return handleError(request, content, HttpStatus.FORBIDDEN);
217 String resourceVersion = headers.getFirst(REQUEST_HEADER_RESOURCE_VERSION);
218 if (resourceVersion == null || resourceVersion.isEmpty()) {
219 return handleError(request, "Request header 'If-Match' missing",
220 HttpStatus.BAD_REQUEST);
223 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
225 document.setVersion(resourceVersion);
227 DocumentOperationResult result = documentStore.deleteDocument(index, document);
228 String output = null;
229 if (!(result.getResultCode() >= 200 && result.getResultCode() <= 299)) { //
230 output = result.getError() != null
231 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
232 : result.getFailureCause();
235 if (httpResponse != null) {
236 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
238 ResponseEntity response;
239 if (output == null) {
240 response = ResponseEntity.status(result.getResultCode()).build();
242 response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
245 logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
247 // Clear the MDC context so that no other transaction inadvertently
248 // uses our transaction id.
249 ApiUtils.clearMdcContext();
252 } catch (Exception e) {
253 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
257 public ResponseEntity<String> processGet(String content, HttpServletRequest request, HttpHeaders headers,
258 HttpServletResponse httpResponse, String index, String id,
259 DocumentStoreInterface documentStore) {
261 // Initialize the MDC Context for logging purposes.
262 ApiUtils.initMdcContext(request, headers);
265 ObjectMapper mapper = new ObjectMapper();
266 mapper.setSerializationInclusion(Include.NON_EMPTY);
269 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
270 ApiUtils.SEARCH_AUTH_POLICY_NAME);
271 } catch (Exception e) {
272 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
273 "DocumentApi.processGet",
275 return handleError(request, content, HttpStatus.FORBIDDEN);
279 return handleError(request, content, HttpStatus.FORBIDDEN);
282 String resourceVersion = headers.getFirst(REQUEST_HEADER_RESOURCE_VERSION);
284 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
286 document.setVersion(resourceVersion);
288 DocumentOperationResult result = documentStore.getDocument(index, document);
289 String output = null;
290 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
291 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
293 output = result.getError() != null
294 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
295 : result.getFailureCause();
297 if (httpResponse != null) {
298 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
300 ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
301 logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
303 // Clear the MDC context so that no other transaction inadvertently
304 // uses our transaction id.
305 ApiUtils.clearMdcContext();
308 } catch (Exception e) {
309 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
313 public ResponseEntity<String> processSearchWithGet(String content, HttpServletRequest request,
314 HttpHeaders headers, String index,
315 String queryText, DocumentStoreInterface documentStore) {
317 // Initialize the MDC Context for logging purposes.
318 ApiUtils.initMdcContext(request, headers);
321 ObjectMapper mapper = new ObjectMapper();
322 mapper.setSerializationInclusion(Include.NON_EMPTY);
326 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
327 ApiUtils.SEARCH_AUTH_POLICY_NAME);
328 } catch (Exception e) {
329 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
330 "processSearchWithGet",
332 return handleError(request, content, HttpStatus.FORBIDDEN);
336 return handleError(request, content, HttpStatus.FORBIDDEN);
339 SearchOperationResult result = documentStore.search(index, queryText);
340 String output = null;
341 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
342 output = mapper.writerWithDefaultPrettyPrinter()
343 .writeValueAsString(result.getSearchResult());
345 output = result.getError() != null
346 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
347 : result.getFailureCause();
349 ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
351 // Clear the MDC context so that no other transaction inadvertently
352 // uses our transaction id.
353 ApiUtils.clearMdcContext();
356 } catch (Exception e) {
357 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
361 public ResponseEntity<String> queryWithGetWithPayload(String content, HttpServletRequest request,
362 HttpHeaders headers, String index,
363 DocumentStoreInterface documentStore) {
365 // Initialize the MDC Context for logging purposes.
366 ApiUtils.initMdcContext(request, headers);
368 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET", (request != null)
369 ? request.getRequestURL ().toString () : "");
370 if (logger.isDebugEnabled()) {
371 logger.debug("Request Body: " + content);
373 return processQuery(index, content, request, headers, documentStore);
376 public ResponseEntity<String> processSearchWithPost(String content, HttpServletRequest request,
377 HttpHeaders headers, String index,
378 DocumentStoreInterface documentStore) {
380 // Initialize the MDC Context for logging purposes.
381 ApiUtils.initMdcContext(request, headers);
383 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST", (request != null)
384 ? request.getRequestURL ().toString () : "");
385 if (logger.isDebugEnabled()) {
386 logger.debug("Request Body: " + content);
389 return processQuery(index, content, request, headers, documentStore);
393 public ResponseEntity<String> processSuggestQueryWithPost(String content, HttpServletRequest request,
394 HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
396 // Initialize the MDC Context for logging purposes.
397 ApiUtils.initMdcContext(request, headers);
399 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST",
400 (request != null) ? request.getRequestURL().toString() : "");
401 if (logger.isDebugEnabled()) {
402 logger.debug("Request Body: " + content);
405 return processSuggestQuery(index, content, request, headers, documentStore);
409 * Common handler for query requests. This is called by both the GET with
410 * payload and POST with payload variants of the query endpoint.
412 * @param index - The index to be queried against.
413 * @param content - The payload containing the query structure.
414 * @param request - The HTTP request.
415 * @param headers - The HTTP headers.
416 * @return - A standard HTTP response.
418 private ResponseEntity processQuery(String index, String content, HttpServletRequest request,
419 HttpHeaders headers, DocumentStoreInterface documentStore) {
422 ObjectMapper mapper = new ObjectMapper();
423 mapper.setSerializationInclusion(Include.NON_EMPTY);
425 // Make sure that we were supplied a payload before proceeding.
426 if (content == null) {
427 return handleError(request, content, HttpStatus.BAD_REQUEST);
430 // Validate that the request has the appropriate authorization.
433 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
434 ApiUtils.SEARCH_AUTH_POLICY_NAME);
436 } catch (Exception e) {
437 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
440 return handleError(request, content, HttpStatus.FORBIDDEN);
444 return handleError(request, content, HttpStatus.FORBIDDEN);
447 SearchStatement searchStatement;
450 // Marshall the supplied request payload into a search statement
452 searchStatement = mapper.readValue(content, SearchStatement.class);
454 } catch (Exception e) {
455 return handleError(request, e.getMessage(), HttpStatus.BAD_REQUEST);
458 // Now, submit the search statement, translated into
459 // ElasticSearch syntax, to the document store DAO.
460 SearchOperationResult result = documentStore.searchWithPayload(index,
461 searchStatement.toElasticSearch());
462 String output = null;
463 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
464 output = prepareOutput(mapper, result);
466 output = result.getError() != null
467 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
468 : result.getFailureCause();
470 ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
472 // Clear the MDC context so that no other transaction inadvertently
473 // uses our transaction id.
474 ApiUtils.clearMdcContext();
478 } catch (Exception e) {
479 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
485 * Common handler for query requests. This is called by both the GET with payload and POST with
486 * payload variants of the query endpoint.
488 * @param index - The index to be queried against.
489 * @param content - The payload containing the query structure.
490 * @param request - The HTTP request.
491 * @param headers - The HTTP headers.
492 * @return - A standard HTTP response.
494 private ResponseEntity<String> processSuggestQuery(String index, String content, HttpServletRequest request,
495 HttpHeaders headers, DocumentStoreInterface documentStore) {
498 ObjectMapper mapper = new ObjectMapper();
499 mapper.setSerializationInclusion(Include.NON_EMPTY);
501 // Make sure that we were supplied a payload before proceeding.
502 if (content == null) {
503 return handleError(request, content, HttpStatus.BAD_REQUEST);
506 // Validate that the request has the appropriate authorization.
509 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
510 ApiUtils.SEARCH_AUTH_POLICY_NAME);
512 } catch (Exception e) {
513 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processQuery", e.getMessage());
514 return handleError(request, content, HttpStatus.FORBIDDEN);
518 return handleError(request, content, HttpStatus.FORBIDDEN);
521 SuggestionStatement suggestionStatement;
524 // Marshall the supplied request payload into a search statement
526 suggestionStatement = mapper.readValue(content, SuggestionStatement.class);
528 } catch (Exception e) {
529 return handleError(request, e.getMessage(), HttpStatus.BAD_REQUEST);
532 // Now, submit the search statement, translated into
533 // ElasticSearch syntax, to the document store DAO.
534 SearchOperationResult result =
535 documentStore.suggestionQueryWithPayload(index, suggestionStatement.toElasticSearch());
536 String output = null;
537 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
538 output = prepareSuggestOutput(mapper, result);
540 output = result.getError() != null
541 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
542 : result.getFailureCause();
544 ResponseEntity<String> response = ResponseEntity.status(result.getResultCode()).body(output);
546 // Clear the MDC context so that no other transaction inadvertently
547 // uses our transaction id.
548 ApiUtils.clearMdcContext();
552 } catch (Exception e) {
553 return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
558 * Checks the supplied HTTP headers to see if we should allow the underlying document
559 * store to implicitly create the index referenced in a document PUT or POST if it
560 * does not already exist in the data store.
562 * @param headers - The HTTP headers to examine.
564 * @return - true if the headers indicate that missing indices should be implicitly created,
567 private boolean implicitlyCreateIndex(HttpHeaders headers) {
569 boolean createIndexIfNotPresent = false;
570 String implicitIndexCreationHeader =
571 headers.getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
573 if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
574 createIndexIfNotPresent = true;
577 return createIndexIfNotPresent;
580 private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
581 throws JsonProcessingException {
582 StringBuffer output = new StringBuffer();
583 output.append("{\r\n\"searchResult\":");
585 mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSearchResult()));
586 AggregationResults aggs = result.getAggregationResult();
588 output.append(",\r\n\"aggregationResult\":");
589 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
590 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
592 output.append("\r\n}");
593 return output.toString();
596 private String prepareSuggestOutput(ObjectMapper mapper, SearchOperationResult result)
597 throws JsonProcessingException {
598 StringBuffer output = new StringBuffer();
599 output.append("{\r\n\"searchResult\":");
601 mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSuggestResult()));
602 AggregationResults aggs = result.getAggregationResult();
604 output.append(",\r\n\"aggregationResult\":");
605 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
606 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
608 output.append("\r\n}");
609 return output.toString();
612 private ResponseEntity handleError( HttpServletRequest request, String message, HttpStatus status) {
613 logResult(request, status);
614 return ResponseEntity.status(status).contentType ( MediaType.APPLICATION_JSON ).body(message);
617 void logResult(HttpServletRequest request, HttpStatus status) {
619 logger.info(SearchDbMsgs.PROCESS_REST_REQUEST, (request != null) ? request.getMethod().toString () : "",
620 (request != null) ? request.getRequestURL ().toString () : "",
621 (request != null) ? request.getRemoteHost () : "", Integer.toString(status.value ()));
623 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
625 .setField(LogLine.DefinedFields.RESPONSE_CODE, status.value())
626 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
627 (request != null) ? request.getMethod().toString () : "",
628 (request != null) ? request.getRequestURL ().toString () : "",
629 (request != null) ? request.getRemoteHost () : "", Integer.toString(status.value()));
631 // Clear the MDC context so that no other transaction inadvertently
632 // uses our transaction id.
633 ApiUtils.clearMdcContext();