2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 AT&T Intellectual Property.
6 * Copyright © 2017 Amdocs
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License ati
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
22 * ECOMP and OpenECOMP are trademarks
23 * and service marks of AT&T Intellectual Property.
25 package org.openecomp.sa.rest;
27 import com.fasterxml.jackson.annotation.JsonInclude.Include;
28 import com.fasterxml.jackson.core.JsonProcessingException;
29 import com.fasterxml.jackson.databind.ObjectMapper;
30 import org.openecomp.cl.api.LogFields;
31 import org.openecomp.cl.api.LogLine;
32 import org.openecomp.cl.api.Logger;
33 import org.openecomp.cl.eelf.LoggerFactory;
34 import org.openecomp.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreDataEntityImpl;
35 import org.openecomp.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreInterface;
36 import org.openecomp.sa.searchdbabstraction.entity.AggregationResults;
37 import org.openecomp.sa.searchdbabstraction.entity.DocumentOperationResult;
38 import org.openecomp.sa.searchdbabstraction.entity.SearchOperationResult;
39 import org.openecomp.sa.searchdbabstraction.logging.SearchDbMsgs;
40 import org.openecomp.sa.searchdbabstraction.searchapi.SearchStatement;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
44 import javax.ws.rs.core.HttpHeaders;
45 import javax.ws.rs.core.MediaType;
46 import javax.ws.rs.core.Response;
47 import javax.ws.rs.core.Response.Status;
49 public class DocumentApi {
50 private static final String REQUEST_HEADER_RESOURCE_VERSION = "If-Match";
51 private static final String RESPONSE_HEADER_RESOURCE_VERSION = "ETag";
52 private static final String REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION = "X-CreateIndex";
54 protected SearchServiceApi searchService = null;
56 private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
57 private Logger auditLogger = LoggerFactory.getInstance()
58 .getAuditLogger(DocumentApi.class.getName());
60 public DocumentApi(SearchServiceApi searchService) {
61 this.searchService = searchService;
64 public Response processPost(String content, HttpServletRequest request, HttpHeaders headers,
65 HttpServletResponse httpResponse, String index,
66 DocumentStoreInterface documentStore) {
68 // Initialize the MDC Context for logging purposes.
69 ApiUtils.initMdcContext(request, headers);
72 ObjectMapper mapper = new ObjectMapper();
73 mapper.setSerializationInclusion(Include.NON_EMPTY);
74 if (content == null) {
75 return handleError(request, content, Status.BAD_REQUEST);
80 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
81 ApiUtils.SEARCH_AUTH_POLICY_NAME);
82 } catch (Exception e) {
83 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
84 "DocumentApi.processPost",
86 return handleError(request, content, Status.FORBIDDEN);
90 return handleError(request, content, Status.FORBIDDEN);
93 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
94 document.setContent(content);
96 DocumentOperationResult result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
98 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
99 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
101 output = result.getError() != null
102 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
103 : result.getFailureCause();
106 if (httpResponse != null) {
107 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
109 Response response = Response.status(result.getResultCode()).entity(output).build();
110 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
112 // Clear the MDC context so that no other transaction inadvertently
113 // uses our transaction id.
114 ApiUtils.clearMdcContext();
117 } catch (Exception e) {
118 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
122 public Response processPut(String content, HttpServletRequest request, HttpHeaders headers,
123 HttpServletResponse httpResponse, String index,
124 String id, DocumentStoreInterface documentStore) {
126 // Initialize the MDC Context for logging purposes.
127 ApiUtils.initMdcContext(request, headers);
130 ObjectMapper mapper = new ObjectMapper();
131 mapper.setSerializationInclusion(Include.NON_EMPTY);
132 if (content == null) {
133 return handleError(request, content, Status.BAD_REQUEST);
138 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.PUT,
139 ApiUtils.SEARCH_AUTH_POLICY_NAME);
140 } catch (Exception e) {
141 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
142 "DocumentApi.processPut",
144 return handleError(request, content, Status.FORBIDDEN);
148 return handleError(request, content, Status.FORBIDDEN);
151 String resourceVersion = headers.getRequestHeaders()
152 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
154 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
156 document.setContent(content);
157 document.setVersion(resourceVersion);
159 DocumentOperationResult result = null;
160 if (resourceVersion == null) {
161 result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
163 result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
166 String output = null;
167 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
168 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
170 output = result.getError() != null
171 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
172 : result.getFailureCause();
174 if (httpResponse != null) {
175 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
177 Response response = Response.status(result.getResultCode()).entity(output).build();
178 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
180 // Clear the MDC context so that no other transaction inadvertently
181 // uses our transaction id.
182 ApiUtils.clearMdcContext();
185 } catch (Exception e) {
186 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
190 public Response processDelete(String content, HttpServletRequest request, HttpHeaders headers,
191 HttpServletResponse httpResponse, String index, String id,
192 DocumentStoreInterface documentStore) {
194 // Initialize the MDC Context for logging purposes.
195 ApiUtils.initMdcContext(request, headers);
198 ObjectMapper mapper = new ObjectMapper();
199 mapper.setSerializationInclusion(Include.NON_EMPTY);
202 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.DELETE,
203 ApiUtils.SEARCH_AUTH_POLICY_NAME);
204 } catch (Exception e) {
205 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
206 "DocumentApi.processDelete",
208 return handleError(request, content, Status.FORBIDDEN);
212 return handleError(request, content, Status.FORBIDDEN);
215 String resourceVersion = headers.getRequestHeaders()
216 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
217 if (resourceVersion == null || resourceVersion.isEmpty()) {
218 return handleError(request, "Request header 'If-Match' missing",
219 javax.ws.rs.core.Response.Status.BAD_REQUEST);
222 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
224 document.setVersion(resourceVersion);
226 DocumentOperationResult result = documentStore.deleteDocument(index, document);
227 String output = null;
228 if (!(result.getResultCode() >= 200 && result.getResultCode() <= 299)) { //
229 output = result.getError() != null
230 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
231 : result.getFailureCause();
234 if (httpResponse != null) {
235 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
238 if (output == null) {
239 response = Response.status(result.getResultCode()).build();
241 response = Response.status(result.getResultCode()).entity(output).build();
244 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
246 // Clear the MDC context so that no other transaction inadvertently
247 // uses our transaction id.
248 ApiUtils.clearMdcContext();
251 } catch (Exception e) {
252 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
256 public Response processGet(String content, HttpServletRequest request, HttpHeaders headers,
257 HttpServletResponse httpResponse, String index, String id,
258 DocumentStoreInterface documentStore) {
260 // Initialize the MDC Context for logging purposes.
261 ApiUtils.initMdcContext(request, headers);
264 ObjectMapper mapper = new ObjectMapper();
265 mapper.setSerializationInclusion(Include.NON_EMPTY);
268 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
269 ApiUtils.SEARCH_AUTH_POLICY_NAME);
270 } catch (Exception e) {
271 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
272 "DocumentApi.processGet",
274 return handleError(request, content, Status.FORBIDDEN);
278 return handleError(request, content, Status.FORBIDDEN);
281 String resourceVersion = headers.getRequestHeaders()
282 .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 Response response = Response.status(result.getResultCode()).entity(output).build();
301 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
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(), Status.INTERNAL_SERVER_ERROR);
313 public Response 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, Status.FORBIDDEN);
336 return handleError(request, content, Status.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 Response response = Response.status(result.getResultCode()).entity(output).build();
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(), Status.INTERNAL_SERVER_ERROR);
361 public Response 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 Response 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 * Common handler for query requests. This is called by both the GET with
394 * payload and POST with payload variants of the query endpoint.
396 * @param index - The index to be queried against.
397 * @param content - The payload containing the query structure.
398 * @param request - The HTTP request.
399 * @param headers - The HTTP headers.
400 * @return - A standard HTTP response.
402 private Response processQuery(String index, String content, HttpServletRequest request,
403 HttpHeaders headers, DocumentStoreInterface documentStore) {
406 ObjectMapper mapper = new ObjectMapper();
407 mapper.setSerializationInclusion(Include.NON_EMPTY);
409 // Make sure that we were supplied a payload before proceeding.
410 if (content == null) {
411 return handleError(request, content, Status.BAD_REQUEST);
414 // Validate that the request has the appropriate authorization.
417 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
418 ApiUtils.SEARCH_AUTH_POLICY_NAME);
420 } catch (Exception e) {
421 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
424 return handleError(request, content, Status.FORBIDDEN);
428 return handleError(request, content, Status.FORBIDDEN);
431 SearchStatement searchStatement;
434 // Marshall the supplied request payload into a search statement
436 searchStatement = mapper.readValue(content, SearchStatement.class);
438 } catch (Exception e) {
439 return handleError(request, e.getMessage(), Status.BAD_REQUEST);
442 // Now, submit the search statement, translated into
443 // ElasticSearch syntax, to the document store DAO.
444 SearchOperationResult result = documentStore.searchWithPayload(index,
445 searchStatement.toElasticSearch());
446 String output = null;
447 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
448 output = prepareOutput(mapper, result);
450 output = result.getError() != null
451 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
452 : result.getFailureCause();
454 Response response = Response.status(result.getResultCode()).entity(output).build();
456 // Clear the MDC context so that no other transaction inadvertently
457 // uses our transaction id.
458 ApiUtils.clearMdcContext();
462 } catch (Exception e) {
463 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
469 * Checks the supplied HTTP headers to see if we should allow the underlying document
470 * store to implicitly create the index referenced in a document PUT or POST if it
471 * does not already exist in the data store.
473 * @param headers - The HTTP headers to examine.
475 * @return - true if the headers indicate that missing indices should be implicitly created,
478 private boolean implicitlyCreateIndex(HttpHeaders headers) {
480 boolean createIndexIfNotPresent = false;
481 String implicitIndexCreationHeader =
482 headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
484 if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
485 createIndexIfNotPresent = true;
488 return createIndexIfNotPresent;
492 private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
493 throws JsonProcessingException {
494 StringBuffer output = new StringBuffer();
495 output.append("{\r\n\"searchResult\":");
496 output.append(mapper.writerWithDefaultPrettyPrinter()
497 .writeValueAsString(result.getSearchResult()));
498 AggregationResults aggs = result.getAggregationResult();
500 output.append(",\r\n\"aggregationResult\":");
501 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
502 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
504 output.append("\r\n}");
505 return output.toString();
508 private Response handleError(HttpServletRequest request, String message, Status status) {
509 logResult(request, status);
510 return Response.status(status).entity(message).type(MediaType.APPLICATION_JSON).build();
513 void logResult(HttpServletRequest request, Response.Status status) {
515 logger.info(SearchDbMsgs.PROCESS_REST_REQUEST, (request != null) ? request.getMethod() : "",
516 (request != null) ? request.getRequestURL().toString() : "",
517 (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
519 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
521 .setField(LogLine.DefinedFields.RESPONSE_CODE, status.getStatusCode())
522 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
523 (request != null) ? request.getMethod() : "",
524 (request != null) ? request.getRequestURL().toString() : "",
525 (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
527 // Clear the MDC context so that no other transaction inadvertently
528 // uses our transaction id.
529 ApiUtils.clearMdcContext();