2 * ============LICENSE_START=======================================================
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
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 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23 package org.openecomp.sa.rest;
25 import com.fasterxml.jackson.annotation.JsonInclude.Include;
26 import com.fasterxml.jackson.core.JsonProcessingException;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import org.openecomp.cl.api.LogFields;
29 import org.openecomp.cl.api.LogLine;
30 import org.openecomp.cl.api.Logger;
31 import org.openecomp.cl.eelf.LoggerFactory;
32 import org.openecomp.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreDataEntityImpl;
33 import org.openecomp.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreInterface;
34 import org.openecomp.sa.searchdbabstraction.entity.AggregationResults;
35 import org.openecomp.sa.searchdbabstraction.entity.DocumentOperationResult;
36 import org.openecomp.sa.searchdbabstraction.entity.SearchOperationResult;
37 import org.openecomp.sa.searchdbabstraction.logging.SearchDbMsgs;
38 import org.openecomp.sa.searchdbabstraction.searchapi.SearchStatement;
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 = LoggerFactory.getInstance()
56 .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,
64 DocumentStoreInterface documentStore) {
66 // Initialize the MDC Context for logging purposes.
67 ApiUtils.initMdcContext(request, headers);
70 ObjectMapper mapper = new ObjectMapper();
71 mapper.setSerializationInclusion(Include.NON_EMPTY);
72 if (content == null) {
73 return handleError(request, content, Status.BAD_REQUEST);
78 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
79 ApiUtils.SEARCH_AUTH_POLICY_NAME);
80 } catch (Exception e) {
81 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
82 "DocumentApi.processPost",
84 return handleError(request, content, Status.FORBIDDEN);
88 return handleError(request, content, Status.FORBIDDEN);
91 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
92 document.setContent(content);
94 DocumentOperationResult result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
96 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
97 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
99 output = result.getError() != null
100 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
101 : result.getFailureCause();
104 if (httpResponse != null) {
105 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
107 Response response = Response.status(result.getResultCode()).entity(output).build();
108 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
110 // Clear the MDC context so that no other transaction inadvertently
111 // uses our transaction id.
112 ApiUtils.clearMdcContext();
115 } catch (Exception e) {
116 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
120 public Response processPut(String content, HttpServletRequest request, HttpHeaders headers,
121 HttpServletResponse httpResponse, String index,
122 String id, DocumentStoreInterface documentStore) {
124 // Initialize the MDC Context for logging purposes.
125 ApiUtils.initMdcContext(request, headers);
128 ObjectMapper mapper = new ObjectMapper();
129 mapper.setSerializationInclusion(Include.NON_EMPTY);
130 if (content == null) {
131 return handleError(request, content, Status.BAD_REQUEST);
136 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.PUT,
137 ApiUtils.SEARCH_AUTH_POLICY_NAME);
138 } catch (Exception e) {
139 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
140 "DocumentApi.processPut",
142 return handleError(request, content, Status.FORBIDDEN);
146 return handleError(request, content, Status.FORBIDDEN);
149 String resourceVersion = headers.getRequestHeaders()
150 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
152 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
154 document.setContent(content);
155 document.setVersion(resourceVersion);
157 DocumentOperationResult result = null;
158 if (resourceVersion == null) {
159 result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
161 result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
164 String output = null;
165 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
166 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
168 output = result.getError() != null
169 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
170 : result.getFailureCause();
172 if (httpResponse != null) {
173 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
175 Response response = Response.status(result.getResultCode()).entity(output).build();
176 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
178 // Clear the MDC context so that no other transaction inadvertently
179 // uses our transaction id.
180 ApiUtils.clearMdcContext();
183 } catch (Exception e) {
184 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
188 public Response processDelete(String content, HttpServletRequest request, HttpHeaders headers,
189 HttpServletResponse httpResponse, String index, String id,
190 DocumentStoreInterface documentStore) {
192 // Initialize the MDC Context for logging purposes.
193 ApiUtils.initMdcContext(request, headers);
196 ObjectMapper mapper = new ObjectMapper();
197 mapper.setSerializationInclusion(Include.NON_EMPTY);
200 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.DELETE,
201 ApiUtils.SEARCH_AUTH_POLICY_NAME);
202 } catch (Exception e) {
203 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
204 "DocumentApi.processDelete",
206 return handleError(request, content, Status.FORBIDDEN);
210 return handleError(request, content, Status.FORBIDDEN);
213 String resourceVersion = headers.getRequestHeaders()
214 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
215 if (resourceVersion == null || resourceVersion.isEmpty()) {
216 return handleError(request, "Request header 'If-Match' missing",
217 javax.ws.rs.core.Response.Status.BAD_REQUEST);
220 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
222 document.setVersion(resourceVersion);
224 DocumentOperationResult result = documentStore.deleteDocument(index, document);
225 String output = null;
226 if (!(result.getResultCode() >= 200 && result.getResultCode() <= 299)) { //
227 output = result.getError() != null
228 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
229 : result.getFailureCause();
232 if (httpResponse != null) {
233 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
236 if (output == null) {
237 response = Response.status(result.getResultCode()).build();
239 response = Response.status(result.getResultCode()).entity(output).build();
242 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
244 // Clear the MDC context so that no other transaction inadvertently
245 // uses our transaction id.
246 ApiUtils.clearMdcContext();
249 } catch (Exception e) {
250 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
254 public Response processGet(String content, HttpServletRequest request, HttpHeaders headers,
255 HttpServletResponse httpResponse, String index, String id,
256 DocumentStoreInterface documentStore) {
258 // Initialize the MDC Context for logging purposes.
259 ApiUtils.initMdcContext(request, headers);
262 ObjectMapper mapper = new ObjectMapper();
263 mapper.setSerializationInclusion(Include.NON_EMPTY);
266 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
267 ApiUtils.SEARCH_AUTH_POLICY_NAME);
268 } catch (Exception e) {
269 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
270 "DocumentApi.processGet",
272 return handleError(request, content, Status.FORBIDDEN);
276 return handleError(request, content, Status.FORBIDDEN);
279 String resourceVersion = headers.getRequestHeaders()
280 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
282 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
284 document.setVersion(resourceVersion);
286 DocumentOperationResult result = documentStore.getDocument(index, document);
287 String output = null;
288 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
289 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
291 output = result.getError() != null
292 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
293 : result.getFailureCause();
295 if (httpResponse != null) {
296 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
298 Response response = Response.status(result.getResultCode()).entity(output).build();
299 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
301 // Clear the MDC context so that no other transaction inadvertently
302 // uses our transaction id.
303 ApiUtils.clearMdcContext();
306 } catch (Exception e) {
307 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
311 public Response processSearchWithGet(String content, HttpServletRequest request,
312 HttpHeaders headers, String index,
313 String queryText, DocumentStoreInterface documentStore) {
315 // Initialize the MDC Context for logging purposes.
316 ApiUtils.initMdcContext(request, headers);
319 ObjectMapper mapper = new ObjectMapper();
320 mapper.setSerializationInclusion(Include.NON_EMPTY);
324 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
325 ApiUtils.SEARCH_AUTH_POLICY_NAME);
326 } catch (Exception e) {
327 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
328 "processSearchWithGet",
330 return handleError(request, content, Status.FORBIDDEN);
334 return handleError(request, content, Status.FORBIDDEN);
337 SearchOperationResult result = documentStore.search(index, queryText);
338 String output = null;
339 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
340 output = mapper.writerWithDefaultPrettyPrinter()
341 .writeValueAsString(result.getSearchResult());
343 output = result.getError() != null
344 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
345 : result.getFailureCause();
347 Response response = Response.status(result.getResultCode()).entity(output).build();
349 // Clear the MDC context so that no other transaction inadvertently
350 // uses our transaction id.
351 ApiUtils.clearMdcContext();
354 } catch (Exception e) {
355 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
359 public Response queryWithGetWithPayload(String content, HttpServletRequest request,
360 HttpHeaders headers, String index,
361 DocumentStoreInterface documentStore) {
363 // Initialize the MDC Context for logging purposes.
364 ApiUtils.initMdcContext(request, headers);
366 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET", (request != null)
367 ? request.getRequestURL().toString() : "");
368 if (logger.isDebugEnabled()) {
369 logger.debug("Request Body: " + content);
371 return processQuery(index, content, request, headers, documentStore);
374 public Response processSearchWithPost(String content, HttpServletRequest request,
375 HttpHeaders headers, String index,
376 DocumentStoreInterface documentStore) {
378 // Initialize the MDC Context for logging purposes.
379 ApiUtils.initMdcContext(request, headers);
381 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST", (request != null)
382 ? request.getRequestURL().toString() : "");
383 if (logger.isDebugEnabled()) {
384 logger.debug("Request Body: " + content);
387 return processQuery(index, content, request, headers, documentStore);
391 * Common handler for query requests. This is called by both the GET with
392 * payload and POST with payload variants of the query endpoint.
394 * @param index - The index to be queried against.
395 * @param content - The payload containing the query structure.
396 * @param request - The HTTP request.
397 * @param headers - The HTTP headers.
398 * @return - A standard HTTP response.
400 private Response processQuery(String index, String content, HttpServletRequest request,
401 HttpHeaders headers, DocumentStoreInterface documentStore) {
404 ObjectMapper mapper = new ObjectMapper();
405 mapper.setSerializationInclusion(Include.NON_EMPTY);
407 // Make sure that we were supplied a payload before proceeding.
408 if (content == null) {
409 return handleError(request, content, Status.BAD_REQUEST);
412 // Validate that the request has the appropriate authorization.
415 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
416 ApiUtils.SEARCH_AUTH_POLICY_NAME);
418 } catch (Exception e) {
419 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
422 return handleError(request, content, Status.FORBIDDEN);
426 return handleError(request, content, Status.FORBIDDEN);
429 SearchStatement searchStatement;
432 // Marshall the supplied request payload into a search statement
434 searchStatement = mapper.readValue(content, SearchStatement.class);
436 } catch (Exception e) {
437 return handleError(request, e.getMessage(), Status.BAD_REQUEST);
440 // Now, submit the search statement, translated into
441 // ElasticSearch syntax, to the document store DAO.
442 SearchOperationResult result = documentStore.searchWithPayload(index,
443 searchStatement.toElasticSearch());
444 String output = null;
445 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
446 output = prepareOutput(mapper, result);
448 output = result.getError() != null
449 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
450 : result.getFailureCause();
452 Response response = Response.status(result.getResultCode()).entity(output).build();
454 // Clear the MDC context so that no other transaction inadvertently
455 // uses our transaction id.
456 ApiUtils.clearMdcContext();
460 } catch (Exception e) {
461 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
467 * Checks the supplied HTTP headers to see if we should allow the underlying document
468 * store to implicitly create the index referenced in a document PUT or POST if it
469 * does not already exist in the data store.
471 * @param headers - The HTTP headers to examine.
473 * @return - true if the headers indicate that missing indices should be implicitly created,
476 private boolean implicitlyCreateIndex(HttpHeaders headers) {
478 boolean createIndexIfNotPresent = false;
479 String implicitIndexCreationHeader =
480 headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
482 if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
483 createIndexIfNotPresent = true;
486 return createIndexIfNotPresent;
490 private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
491 throws JsonProcessingException {
492 StringBuffer output = new StringBuffer();
493 output.append("{\r\n\"searchResult\":");
494 output.append(mapper.writerWithDefaultPrettyPrinter()
495 .writeValueAsString(result.getSearchResult()));
496 AggregationResults aggs = result.getAggregationResult();
498 output.append(",\r\n\"aggregationResult\":");
499 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
500 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
502 output.append("\r\n}");
503 return output.toString();
506 private Response handleError(HttpServletRequest request, String message, Status status) {
507 logResult(request, status);
508 return Response.status(status).entity(message).type(MediaType.APPLICATION_JSON).build();
511 void logResult(HttpServletRequest request, Response.Status status) {
513 logger.info(SearchDbMsgs.PROCESS_REST_REQUEST, (request != null) ? request.getMethod() : "",
514 (request != null) ? request.getRequestURL().toString() : "",
515 (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
517 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
519 .setField(LogLine.DefinedFields.RESPONSE_CODE, status.getStatusCode())
520 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
521 (request != null) ? request.getMethod() : "",
522 (request != null) ? request.getRequestURL().toString() : "",
523 (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
525 // Clear the MDC context so that no other transaction inadvertently
526 // uses our transaction id.
527 ApiUtils.clearMdcContext();