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;
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.sa.searchdbabstraction.searchapi.SuggestionStatement;
35 import org.onap.aai.cl.api.LogFields;
36 import org.onap.aai.cl.api.LogLine;
37 import org.onap.aai.cl.api.Logger;
38 import org.onap.aai.cl.eelf.LoggerFactory;
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;
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";
52 protected SearchServiceApi searchService = null;
54 private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
55 private Logger auditLogger =
56 LoggerFactory.getInstance().getAuditLogger(DocumentApi.class.getName());
58 public DocumentApi(SearchServiceApi searchService) {
59 this.searchService = searchService;
62 public Response processPost(String content, HttpServletRequest request, HttpHeaders headers,
63 HttpServletResponse httpResponse, String index, DocumentStoreInterface documentStore) {
65 // Initialize the MDC Context for logging purposes.
66 ApiUtils.initMdcContext(request, headers);
69 ObjectMapper mapper = new ObjectMapper();
70 mapper.setSerializationInclusion(Include.NON_EMPTY);
71 if (content == null) {
72 return handleError(request, content, Status.BAD_REQUEST);
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, "DocumentApi.processPost",
82 return handleError(request, content, Status.FORBIDDEN);
86 return handleError(request, content, Status.FORBIDDEN);
89 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
90 document.setContent(content);
92 DocumentOperationResult result =
93 documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
95 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
96 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
98 output = result.getError() != null
99 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
100 : result.getFailureCause();
103 if (httpResponse != null) {
104 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
106 Response response = Response.status(result.getResultCode()).entity(output).build();
107 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
109 // Clear the MDC context so that no other transaction inadvertently
110 // uses our transaction id.
111 ApiUtils.clearMdcContext();
114 } catch (Exception e) {
115 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
119 public Response processPut(String content, HttpServletRequest request, HttpHeaders headers,
120 HttpServletResponse httpResponse, String index, String id,
121 DocumentStoreInterface documentStore) {
123 // Initialize the MDC Context for logging purposes.
124 ApiUtils.initMdcContext(request, headers);
127 ObjectMapper mapper = new ObjectMapper();
128 mapper.setSerializationInclusion(Include.NON_EMPTY);
129 if (content == null) {
130 return handleError(request, content, Status.BAD_REQUEST);
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, "DocumentApi.processPut",
140 return handleError(request, content, Status.FORBIDDEN);
144 return handleError(request, content, Status.FORBIDDEN);
147 String resourceVersion =
148 headers.getRequestHeaders().getFirst(REQUEST_HEADER_RESOURCE_VERSION);
150 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
152 document.setContent(content);
153 document.setVersion(resourceVersion);
155 DocumentOperationResult result = null;
156 if (resourceVersion == null) {
157 result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
159 result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
162 String output = null;
163 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
164 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
166 output = result.getError() != null
167 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
168 : result.getFailureCause();
170 if (httpResponse != null) {
171 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
173 Response response = Response.status(result.getResultCode()).entity(output).build();
174 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
176 // Clear the MDC context so that no other transaction inadvertently
177 // uses our transaction id.
178 ApiUtils.clearMdcContext();
181 } catch (Exception e) {
182 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
186 public Response processDelete(String content, HttpServletRequest request, HttpHeaders headers,
187 HttpServletResponse httpResponse, String index, String id,
188 DocumentStoreInterface documentStore) {
190 // Initialize the MDC Context for logging purposes.
191 ApiUtils.initMdcContext(request, headers);
194 ObjectMapper mapper = new ObjectMapper();
195 mapper.setSerializationInclusion(Include.NON_EMPTY);
198 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.DELETE,
199 ApiUtils.SEARCH_AUTH_POLICY_NAME);
200 } catch (Exception e) {
201 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "DocumentApi.processDelete",
203 return handleError(request, content, Status.FORBIDDEN);
207 return handleError(request, content, Status.FORBIDDEN);
210 String resourceVersion =
211 headers.getRequestHeaders().getFirst(REQUEST_HEADER_RESOURCE_VERSION);
212 if (resourceVersion == null || resourceVersion.isEmpty()) {
213 return handleError(request, "Request header 'If-Match' missing",
214 javax.ws.rs.core.Response.Status.BAD_REQUEST);
217 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
219 document.setVersion(resourceVersion);
221 DocumentOperationResult result = documentStore.deleteDocument(index, document);
222 String output = null;
223 if (!(result.getResultCode() >= 200 && result.getResultCode() <= 299)) { //
224 output = result.getError() != null
225 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
226 : result.getFailureCause();
229 if (httpResponse != null) {
230 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
233 if (output == null) {
234 response = Response.status(result.getResultCode()).build();
236 response = Response.status(result.getResultCode()).entity(output).build();
239 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
241 // Clear the MDC context so that no other transaction inadvertently
242 // uses our transaction id.
243 ApiUtils.clearMdcContext();
246 } catch (Exception e) {
247 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
251 public Response processGet(String content, HttpServletRequest request, HttpHeaders headers,
252 HttpServletResponse httpResponse, String index, String id,
253 DocumentStoreInterface documentStore) {
255 // Initialize the MDC Context for logging purposes.
256 ApiUtils.initMdcContext(request, headers);
259 ObjectMapper mapper = new ObjectMapper();
260 mapper.setSerializationInclusion(Include.NON_EMPTY);
263 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
264 ApiUtils.SEARCH_AUTH_POLICY_NAME);
265 } catch (Exception e) {
266 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "DocumentApi.processGet",
268 return handleError(request, content, Status.FORBIDDEN);
272 return handleError(request, content, Status.FORBIDDEN);
275 String resourceVersion =
276 headers.getRequestHeaders().getFirst(REQUEST_HEADER_RESOURCE_VERSION);
278 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
280 document.setVersion(resourceVersion);
282 DocumentOperationResult result = documentStore.getDocument(index, document);
283 String output = null;
284 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
285 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
287 output = result.getError() != null
288 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
289 : result.getFailureCause();
291 if (httpResponse != null) {
292 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
294 Response response = Response.status(result.getResultCode()).entity(output).build();
295 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
297 // Clear the MDC context so that no other transaction inadvertently
298 // uses our transaction id.
299 ApiUtils.clearMdcContext();
302 } catch (Exception e) {
303 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
307 public Response processSearchWithGet(String content, HttpServletRequest request,
308 HttpHeaders headers, String index, String queryText, DocumentStoreInterface documentStore) {
310 // Initialize the MDC Context for logging purposes.
311 ApiUtils.initMdcContext(request, headers);
314 ObjectMapper mapper = new ObjectMapper();
315 mapper.setSerializationInclusion(Include.NON_EMPTY);
319 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
320 ApiUtils.SEARCH_AUTH_POLICY_NAME);
321 } catch (Exception e) {
322 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processSearchWithGet",
324 return handleError(request, content, Status.FORBIDDEN);
328 return handleError(request, content, Status.FORBIDDEN);
331 SearchOperationResult result = documentStore.search(index, queryText);
332 String output = null;
333 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
335 mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSearchResult());
337 output = result.getError() != null
338 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
339 : result.getFailureCause();
341 Response response = Response.status(result.getResultCode()).entity(output).build();
343 // Clear the MDC context so that no other transaction inadvertently
344 // uses our transaction id.
345 ApiUtils.clearMdcContext();
348 } catch (Exception e) {
349 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
353 public Response queryWithGetWithPayload(String content, HttpServletRequest request,
354 HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
356 // Initialize the MDC Context for logging purposes.
357 ApiUtils.initMdcContext(request, headers);
359 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET",
360 (request != null) ? request.getRequestURL().toString() : "");
361 if (logger.isDebugEnabled()) {
362 logger.debug("Request Body: " + content);
364 return processQuery(index, content, request, headers, documentStore);
367 public Response processSearchWithPost(String content, HttpServletRequest request,
368 HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
370 // Initialize the MDC Context for logging purposes.
371 ApiUtils.initMdcContext(request, headers);
373 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST",
374 (request != null) ? request.getRequestURL().toString() : "");
375 if (logger.isDebugEnabled()) {
376 logger.debug("Request Body: " + content);
379 return processQuery(index, content, request, headers, documentStore);
382 public Response processSuggestQueryWithPost(String content, HttpServletRequest request,
383 HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
385 // Initialize the MDC Context for logging purposes.
386 ApiUtils.initMdcContext(request, headers);
388 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST",
389 (request != null) ? request.getRequestURL().toString() : "");
390 if (logger.isDebugEnabled()) {
391 logger.debug("Request Body: " + content);
394 return processSuggestQuery(index, content, request, headers, documentStore);
398 * Common handler for query requests. This is called by both the GET with payload and POST with
399 * payload variants of the query endpoint.
401 * @param index - The index to be queried against.
402 * @param content - The payload containing the query structure.
403 * @param request - The HTTP request.
404 * @param headers - The HTTP headers.
405 * @return - A standard HTTP response.
407 private Response processQuery(String index, String content, HttpServletRequest request,
408 HttpHeaders headers, DocumentStoreInterface documentStore) {
411 ObjectMapper mapper = new ObjectMapper();
412 mapper.setSerializationInclusion(Include.NON_EMPTY);
414 // Make sure that we were supplied a payload before proceeding.
415 if (content == null) {
416 return handleError(request, content, Status.BAD_REQUEST);
419 // Validate that the request has the appropriate authorization.
422 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
423 ApiUtils.SEARCH_AUTH_POLICY_NAME);
425 } catch (Exception e) {
426 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processQuery", e.getMessage());
427 return handleError(request, content, Status.FORBIDDEN);
431 return handleError(request, content, Status.FORBIDDEN);
434 SearchStatement searchStatement;
437 // Marshall the supplied request payload into a search statement
439 searchStatement = mapper.readValue(content, SearchStatement.class);
441 } catch (Exception e) {
442 return handleError(request, e.getMessage(), Status.BAD_REQUEST);
445 // Now, submit the search statement, translated into
446 // ElasticSearch syntax, to the document store DAO.
447 SearchOperationResult result =
448 documentStore.searchWithPayload(index, searchStatement.toElasticSearch());
449 String output = null;
450 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
451 output = prepareOutput(mapper, result);
453 output = result.getError() != null
454 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
455 : result.getFailureCause();
457 Response response = Response.status(result.getResultCode()).entity(output).build();
459 // Clear the MDC context so that no other transaction inadvertently
460 // uses our transaction id.
461 ApiUtils.clearMdcContext();
465 } catch (Exception e) {
466 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
471 * Common handler for query requests. This is called by both the GET with payload and POST with
472 * payload variants of the query endpoint.
474 * @param index - The index to be queried against.
475 * @param content - The payload containing the query structure.
476 * @param request - The HTTP request.
477 * @param headers - The HTTP headers.
478 * @return - A standard HTTP response.
480 private Response processSuggestQuery(String index, String content, HttpServletRequest request,
481 HttpHeaders headers, DocumentStoreInterface documentStore) {
484 ObjectMapper mapper = new ObjectMapper();
485 mapper.setSerializationInclusion(Include.NON_EMPTY);
487 // Make sure that we were supplied a payload before proceeding.
488 if (content == null) {
489 return handleError(request, content, Status.BAD_REQUEST);
492 // Validate that the request has the appropriate authorization.
495 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
496 ApiUtils.SEARCH_AUTH_POLICY_NAME);
498 } catch (Exception e) {
499 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processQuery", e.getMessage());
500 return handleError(request, content, Status.FORBIDDEN);
504 return handleError(request, content, Status.FORBIDDEN);
507 SuggestionStatement suggestionStatement;
510 // Marshall the supplied request payload into a search statement
512 suggestionStatement = mapper.readValue(content, SuggestionStatement.class);
514 } catch (Exception e) {
515 return handleError(request, e.getMessage(), Status.BAD_REQUEST);
518 // Now, submit the search statement, translated into
519 // ElasticSearch syntax, to the document store DAO.
520 SearchOperationResult result =
521 documentStore.suggestionQueryWithPayload(index, suggestionStatement.toElasticSearch());
522 String output = null;
523 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
524 output = prepareSuggestOutput(mapper, result);
526 output = result.getError() != null
527 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
528 : result.getFailureCause();
530 Response response = Response.status(result.getResultCode()).entity(output).build();
532 // Clear the MDC context so that no other transaction inadvertently
533 // uses our transaction id.
534 ApiUtils.clearMdcContext();
538 } catch (Exception e) {
539 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
544 * Checks the supplied HTTP headers to see if we should allow the underlying document store to
545 * implicitly create the index referenced in a document PUT or POST if it does not already exist
548 * @param headers - The HTTP headers to examine.
550 * @return - true if the headers indicate that missing indices should be implicitly created, false
553 private boolean implicitlyCreateIndex(HttpHeaders headers) {
555 boolean createIndexIfNotPresent = false;
556 String implicitIndexCreationHeader =
557 headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
559 if ((implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true"))) {
560 createIndexIfNotPresent = true;
563 return createIndexIfNotPresent;
566 private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
567 throws JsonProcessingException {
568 StringBuffer output = new StringBuffer();
569 output.append("{\r\n\"searchResult\":");
571 mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSearchResult()));
572 AggregationResults aggs = result.getAggregationResult();
574 output.append(",\r\n\"aggregationResult\":");
575 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
576 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
578 output.append("\r\n}");
579 return output.toString();
582 private String prepareSuggestOutput(ObjectMapper mapper, SearchOperationResult result)
583 throws JsonProcessingException {
584 StringBuffer output = new StringBuffer();
585 output.append("{\r\n\"searchResult\":");
587 mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSuggestResult()));
588 AggregationResults aggs = result.getAggregationResult();
590 output.append(",\r\n\"aggregationResult\":");
591 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
592 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
594 output.append("\r\n}");
595 return output.toString();
598 private Response handleError(HttpServletRequest request, String message, Status status) {
599 logResult(request, status);
600 return Response.status(status).entity(message).type(MediaType.APPLICATION_JSON).build();
603 void logResult(HttpServletRequest request, Response.Status status) {
605 logger.info(SearchDbMsgs.PROCESS_REST_REQUEST, (request != null) ? request.getMethod() : "",
606 (request != null) ? request.getRequestURL().toString() : "",
607 (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
609 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
610 new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, status.getStatusCode())
611 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
612 (request != null) ? request.getMethod() : "",
613 (request != null) ? request.getRequestURL().toString() : "",
614 (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
616 // Clear the MDC context so that no other transaction inadvertently
617 // uses our transaction id.
618 ApiUtils.clearMdcContext();