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.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;
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;
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";
51 protected SearchServiceApi searchService = null;
53 private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
54 private Logger auditLogger = LoggerFactory.getInstance()
55 .getAuditLogger(DocumentApi.class.getName());
57 public DocumentApi(SearchServiceApi searchService) {
58 this.searchService = searchService;
61 public Response processPost(String content, HttpServletRequest request, HttpHeaders headers,
62 HttpServletResponse httpResponse, String index,
63 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,
81 "DocumentApi.processPost",
83 return handleError(request, content, Status.FORBIDDEN);
87 return handleError(request, content, Status.FORBIDDEN);
90 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
91 document.setContent(content);
93 DocumentOperationResult result = 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,
121 String id, 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,
139 "DocumentApi.processPut",
141 return handleError(request, content, Status.FORBIDDEN);
145 return handleError(request, content, Status.FORBIDDEN);
148 String resourceVersion = headers.getRequestHeaders()
149 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
151 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
153 document.setContent(content);
154 document.setVersion(resourceVersion);
156 DocumentOperationResult result = null;
157 if (resourceVersion == null) {
158 result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
160 result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
163 String output = null;
164 if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
165 output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
167 output = result.getError() != null
168 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
169 : result.getFailureCause();
171 if (httpResponse != null) {
172 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
174 Response response = Response.status(result.getResultCode()).entity(output).build();
175 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
177 // Clear the MDC context so that no other transaction inadvertently
178 // uses our transaction id.
179 ApiUtils.clearMdcContext();
182 } catch (Exception e) {
183 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
187 public Response processDelete(String content, HttpServletRequest request, HttpHeaders headers,
188 HttpServletResponse httpResponse, String index, String id,
189 DocumentStoreInterface documentStore) {
191 // Initialize the MDC Context for logging purposes.
192 ApiUtils.initMdcContext(request, headers);
195 ObjectMapper mapper = new ObjectMapper();
196 mapper.setSerializationInclusion(Include.NON_EMPTY);
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",
205 return handleError(request, content, Status.FORBIDDEN);
209 return handleError(request, content, Status.FORBIDDEN);
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);
219 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
221 document.setVersion(resourceVersion);
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();
231 if (httpResponse != null) {
232 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
235 if (output == null) {
236 response = Response.status(result.getResultCode()).build();
238 response = Response.status(result.getResultCode()).entity(output).build();
241 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
243 // Clear the MDC context so that no other transaction inadvertently
244 // uses our transaction id.
245 ApiUtils.clearMdcContext();
248 } catch (Exception e) {
249 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
253 public Response processGet(String content, HttpServletRequest request, HttpHeaders headers,
254 HttpServletResponse httpResponse, String index, String id,
255 DocumentStoreInterface documentStore) {
257 // Initialize the MDC Context for logging purposes.
258 ApiUtils.initMdcContext(request, headers);
261 ObjectMapper mapper = new ObjectMapper();
262 mapper.setSerializationInclusion(Include.NON_EMPTY);
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",
271 return handleError(request, content, Status.FORBIDDEN);
275 return handleError(request, content, Status.FORBIDDEN);
278 String resourceVersion = headers.getRequestHeaders()
279 .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
281 DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
283 document.setVersion(resourceVersion);
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());
290 output = result.getError() != null
291 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
292 : result.getFailureCause();
294 if (httpResponse != null) {
295 httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
297 Response response = Response.status(result.getResultCode()).entity(output).build();
298 logResult(request, Response.Status.fromStatusCode(response.getStatus()));
300 // Clear the MDC context so that no other transaction inadvertently
301 // uses our transaction id.
302 ApiUtils.clearMdcContext();
305 } catch (Exception e) {
306 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
310 public Response processSearchWithGet(String content, HttpServletRequest request,
311 HttpHeaders headers, String index,
312 String queryText, DocumentStoreInterface documentStore) {
314 // Initialize the MDC Context for logging purposes.
315 ApiUtils.initMdcContext(request, headers);
318 ObjectMapper mapper = new ObjectMapper();
319 mapper.setSerializationInclusion(Include.NON_EMPTY);
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",
329 return handleError(request, content, Status.FORBIDDEN);
333 return handleError(request, content, Status.FORBIDDEN);
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());
342 output = result.getError() != null
343 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
344 : result.getFailureCause();
346 Response response = Response.status(result.getResultCode()).entity(output).build();
348 // Clear the MDC context so that no other transaction inadvertently
349 // uses our transaction id.
350 ApiUtils.clearMdcContext();
353 } catch (Exception e) {
354 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
358 public Response queryWithGetWithPayload(String content, HttpServletRequest request,
359 HttpHeaders headers, String index,
360 DocumentStoreInterface documentStore) {
362 // Initialize the MDC Context for logging purposes.
363 ApiUtils.initMdcContext(request, headers);
365 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET", (request != null)
366 ? request.getRequestURL().toString() : "");
367 if (logger.isDebugEnabled()) {
368 logger.debug("Request Body: " + content);
370 return processQuery(index, content, request, headers, documentStore);
373 public Response processSearchWithPost(String content, HttpServletRequest request,
374 HttpHeaders headers, String index,
375 DocumentStoreInterface documentStore) {
377 // Initialize the MDC Context for logging purposes.
378 ApiUtils.initMdcContext(request, headers);
380 logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST", (request != null)
381 ? request.getRequestURL().toString() : "");
382 if (logger.isDebugEnabled()) {
383 logger.debug("Request Body: " + content);
386 return processQuery(index, content, request, headers, documentStore);
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.
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.
399 private Response processQuery(String index, String content, HttpServletRequest request,
400 HttpHeaders headers, DocumentStoreInterface documentStore) {
403 ObjectMapper mapper = new ObjectMapper();
404 mapper.setSerializationInclusion(Include.NON_EMPTY);
406 // Make sure that we were supplied a payload before proceeding.
407 if (content == null) {
408 return handleError(request, content, Status.BAD_REQUEST);
411 // Validate that the request has the appropriate authorization.
414 isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
415 ApiUtils.SEARCH_AUTH_POLICY_NAME);
417 } catch (Exception e) {
418 logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
421 return handleError(request, content, Status.FORBIDDEN);
425 return handleError(request, content, Status.FORBIDDEN);
428 SearchStatement searchStatement;
431 // Marshall the supplied request payload into a search statement
433 searchStatement = mapper.readValue(content, SearchStatement.class);
435 } catch (Exception e) {
436 return handleError(request, e.getMessage(), Status.BAD_REQUEST);
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);
447 output = result.getError() != null
448 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
449 : result.getFailureCause();
451 Response response = Response.status(result.getResultCode()).entity(output).build();
453 // Clear the MDC context so that no other transaction inadvertently
454 // uses our transaction id.
455 ApiUtils.clearMdcContext();
459 } catch (Exception e) {
460 return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
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.
470 * @param headers - The HTTP headers to examine.
472 * @return - true if the headers indicate that missing indices should be implicitly created,
475 private boolean implicitlyCreateIndex(HttpHeaders headers) {
477 boolean createIndexIfNotPresent = false;
478 String implicitIndexCreationHeader =
479 headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
481 if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
482 createIndexIfNotPresent = true;
485 return createIndexIfNotPresent;
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();
497 output.append(",\r\n\"aggregationResult\":");
498 output.append(mapper.setSerializationInclusion(Include.NON_NULL)
499 .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
501 output.append("\r\n}");
502 return output.toString();
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();
510 void logResult(HttpServletRequest request, Response.Status status) {
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()));
516 auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
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()));
524 // Clear the MDC context so that no other transaction inadvertently
525 // uses our transaction id.
526 ApiUtils.clearMdcContext();