ac7610b2918d70ee353a33779543a839497dccc9
[aai/search-data-service.git] / src / main / java / org / onap / aai / sa / rest / DocumentApi.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
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
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  */
21 package org.onap.aai.sa.rest;
22
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;
42
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;
49 // Spring Imports
50
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";
55   
56   protected SearchServiceApi searchService = null;
57
58   private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
59   private Logger auditLogger = LoggerFactory.getInstance()
60       .getAuditLogger(DocumentApi.class.getName());
61
62   public DocumentApi(SearchServiceApi searchService) {
63     this.searchService = searchService;
64   }
65
66   public ResponseEntity<String> processPost(String content, HttpServletRequest request, HttpHeaders headers,
67                               HttpServletResponse httpResponse, String index,
68                               DocumentStoreInterface documentStore) {
69
70     // Initialize the MDC Context for logging purposes.
71     ApiUtils.initMdcContext(request, headers);
72
73     try {
74       ObjectMapper mapper = new ObjectMapper();
75       mapper.setSerializationInclusion(Include.NON_EMPTY);
76       if (content == null) {
77         return handleError(request, content, HttpStatus.BAD_REQUEST);
78       }
79
80       boolean isValid;
81       try {
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",
87             e.getMessage());
88         return handleError(request, content, HttpStatus.FORBIDDEN);
89       }
90
91       if (!isValid) {
92         return handleError(request, content, HttpStatus.FORBIDDEN);
93       }
94
95       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
96       document.setContent(content);
97
98       DocumentOperationResult result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
99       String output = null;
100       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
101         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
102       } else {
103         output = result.getError() != null
104             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
105             : result.getFailureCause();
106       }
107
108       if (httpResponse != null) {
109         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
110       }
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 () ));
114
115       // Clear the MDC context so that no other transaction inadvertently
116       // uses our transaction id.
117       ApiUtils.clearMdcContext();
118
119       return response;
120     } catch (Exception e) {
121       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
122     }
123   }
124
125   public ResponseEntity<String> processPut(String content, HttpServletRequest request, HttpHeaders headers,
126                              HttpServletResponse httpResponse, String index,
127                              String id, DocumentStoreInterface documentStore) {
128
129     // Initialize the MDC Context for logging purposes.
130     ApiUtils.initMdcContext(request, headers);
131
132     try {
133       ObjectMapper mapper = new ObjectMapper();
134       mapper.setSerializationInclusion(Include.NON_EMPTY);
135       if (content == null) {
136         return handleError(request, content, HttpStatus.BAD_REQUEST);
137       }
138
139       boolean isValid;
140       try {
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",
146             e.getMessage());
147         return handleError(request, content, HttpStatus.FORBIDDEN);
148       }
149
150       if (!isValid) {
151         return handleError(request, content, HttpStatus.FORBIDDEN);
152       }
153
154       String resourceVersion = headers.getFirst(REQUEST_HEADER_RESOURCE_VERSION);
155
156       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
157       document.setId(id);
158       document.setContent(content);
159       document.setVersion(resourceVersion);
160
161       DocumentOperationResult result = null;
162       if (resourceVersion == null) {
163         result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
164       } else {
165         result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
166       }
167
168       String output = null;
169       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
170         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
171       } else {
172         output = result.getError() != null
173             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
174             : result.getFailureCause();
175       }
176       if (httpResponse != null) {
177         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
178       }
179       ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
180       logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
181
182       // Clear the MDC context so that no other transaction inadvertently
183       // uses our transaction id.
184       ApiUtils.clearMdcContext();
185
186       return response;
187     } catch (Exception e) {
188       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
189     }
190   }
191
192   public ResponseEntity<String> processDelete(String content, HttpServletRequest request, HttpHeaders headers,
193                                 HttpServletResponse httpResponse, String index, String id,
194                                 DocumentStoreInterface documentStore) {
195
196     // Initialize the MDC Context for logging purposes.
197     ApiUtils.initMdcContext(request, headers);
198
199     try {
200       ObjectMapper mapper = new ObjectMapper();
201       mapper.setSerializationInclusion(Include.NON_EMPTY);
202       boolean isValid;
203       try {
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",
209             e.getMessage());
210         return handleError(request, content, HttpStatus.FORBIDDEN);
211       }
212
213       if (!isValid) {
214         return handleError(request, content, HttpStatus.FORBIDDEN);
215       }
216
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);
221       }
222
223       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
224       document.setId(id);
225       document.setVersion(resourceVersion);
226
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();
233       }
234
235       if (httpResponse != null) {
236         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
237       }
238       ResponseEntity response;
239       if (output == null) {
240         response = ResponseEntity.status(result.getResultCode()).build();
241       } else {
242         response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
243       }
244
245       logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
246
247       // Clear the MDC context so that no other transaction inadvertently
248       // uses our transaction id.
249       ApiUtils.clearMdcContext();
250
251       return response;
252     } catch (Exception e) {
253       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
254     }
255   }
256
257   public ResponseEntity<String> processGet(String content, HttpServletRequest request, HttpHeaders headers,
258                              HttpServletResponse httpResponse, String index, String id,
259                              DocumentStoreInterface documentStore) {
260
261     // Initialize the MDC Context for logging purposes.
262     ApiUtils.initMdcContext(request, headers);
263
264     try {
265       ObjectMapper mapper = new ObjectMapper();
266       mapper.setSerializationInclusion(Include.NON_EMPTY);
267       boolean isValid;
268       try {
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",
274             e.getMessage());
275         return handleError(request, content, HttpStatus.FORBIDDEN);
276       }
277
278       if (!isValid) {
279         return handleError(request, content, HttpStatus.FORBIDDEN);
280       }
281
282       String resourceVersion = headers.getFirst(REQUEST_HEADER_RESOURCE_VERSION);
283
284       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
285       document.setId(id);
286       document.setVersion(resourceVersion);
287
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());
292       } else {
293         output = result.getError() != null
294             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
295             : result.getFailureCause();
296       }
297       if (httpResponse != null) {
298         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
299       }
300       ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
301       logResult(request, HttpStatus.valueOf ( response.getStatusCodeValue () ));
302
303       // Clear the MDC context so that no other transaction inadvertently
304       // uses our transaction id.
305       ApiUtils.clearMdcContext();
306
307       return response;
308     } catch (Exception e) {
309       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
310     }
311   }
312
313   public ResponseEntity<String> processSearchWithGet(String content, HttpServletRequest request,
314                                        HttpHeaders headers, String index,
315                                        String queryText, DocumentStoreInterface documentStore) {
316
317     // Initialize the MDC Context for logging purposes.
318     ApiUtils.initMdcContext(request, headers);
319
320     try {
321       ObjectMapper mapper = new ObjectMapper();
322       mapper.setSerializationInclusion(Include.NON_EMPTY);
323
324       boolean isValid;
325       try {
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",
331             e.getMessage());
332         return handleError(request, content, HttpStatus.FORBIDDEN);
333       }
334
335       if (!isValid) {
336         return handleError(request, content, HttpStatus.FORBIDDEN);
337       }
338
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());
344       } else {
345         output = result.getError() != null
346             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
347             : result.getFailureCause();
348       }
349       ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
350
351       // Clear the MDC context so that no other transaction inadvertently
352       // uses our transaction id.
353       ApiUtils.clearMdcContext();
354
355       return response;
356     } catch (Exception e) {
357       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
358     }
359   }
360
361   public ResponseEntity<String> queryWithGetWithPayload(String content, HttpServletRequest request,
362                                           HttpHeaders headers, String index,
363                                           DocumentStoreInterface documentStore) {
364
365     // Initialize the MDC Context for logging purposes.
366     ApiUtils.initMdcContext(request, headers);
367
368     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET", (request != null)
369         ? request.getRequestURL ().toString () : "");
370     if (logger.isDebugEnabled()) {
371       logger.debug("Request Body: " + content);
372     }
373     return processQuery(index, content, request, headers, documentStore);
374   }
375
376   public ResponseEntity<String> processSearchWithPost(String content, HttpServletRequest request,
377                                         HttpHeaders headers, String index,
378                                         DocumentStoreInterface documentStore) {
379
380     // Initialize the MDC Context for logging purposes.
381     ApiUtils.initMdcContext(request, headers);
382
383     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST", (request != null)
384         ? request.getRequestURL ().toString () : "");
385     if (logger.isDebugEnabled()) {
386       logger.debug("Request Body: " + content);
387     }
388
389     return processQuery(index, content, request, headers, documentStore);
390   }
391
392
393   public ResponseEntity<String> processSuggestQueryWithPost(String content, HttpServletRequest request,
394                                               HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
395
396     // Initialize the MDC Context for logging purposes.
397     ApiUtils.initMdcContext(request, headers);
398
399     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST",
400             (request != null) ? request.getRequestURL().toString() : "");
401     if (logger.isDebugEnabled()) {
402       logger.debug("Request Body: " + content);
403     }
404
405     return processSuggestQuery(index, content, request, headers, documentStore);
406   }
407
408   /**
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.
411    *
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.
417    */
418   private ResponseEntity processQuery(String index, String content, HttpServletRequest request,
419                                 HttpHeaders headers, DocumentStoreInterface documentStore) {
420
421     try {
422       ObjectMapper mapper = new ObjectMapper();
423       mapper.setSerializationInclusion(Include.NON_EMPTY);
424
425       // Make sure that we were supplied a payload before proceeding.
426       if (content == null) {
427         return handleError(request, content, HttpStatus.BAD_REQUEST);
428       }
429
430       // Validate that the request has the appropriate authorization.
431       boolean isValid;
432       try {
433         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
434             ApiUtils.SEARCH_AUTH_POLICY_NAME);
435
436       } catch (Exception e) {
437         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
438             "processQuery",
439             e.getMessage());
440         return handleError(request, content, HttpStatus.FORBIDDEN);
441       }
442
443       if (!isValid) {
444         return handleError(request, content, HttpStatus.FORBIDDEN);
445       }
446
447       SearchStatement searchStatement;
448
449       try {
450         // Marshall the supplied request payload into a search statement
451         // object.
452         searchStatement = mapper.readValue(content, SearchStatement.class);
453
454       } catch (Exception e) {
455         return handleError(request, e.getMessage(), HttpStatus.BAD_REQUEST);
456       }
457
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);
465       } else {
466         output = result.getError() != null
467             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
468             : result.getFailureCause();
469       }
470       ResponseEntity response = ResponseEntity.status(result.getResultCode()).contentType ( MediaType.APPLICATION_JSON ).body(output);
471
472       // Clear the MDC context so that no other transaction inadvertently
473       // uses our transaction id.
474       ApiUtils.clearMdcContext();
475
476       return response;
477
478     } catch (Exception e) {
479       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
480     }
481   }
482
483
484   /**
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.
487    *
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.
493    */
494   private ResponseEntity<String> processSuggestQuery(String index, String content, HttpServletRequest request,
495                                        HttpHeaders headers, DocumentStoreInterface documentStore) {
496
497     try {
498       ObjectMapper mapper = new ObjectMapper();
499       mapper.setSerializationInclusion(Include.NON_EMPTY);
500
501       // Make sure that we were supplied a payload before proceeding.
502       if (content == null) {
503         return handleError(request, content, HttpStatus.BAD_REQUEST);
504       }
505
506       // Validate that the request has the appropriate authorization.
507       boolean isValid;
508       try {
509         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
510                 ApiUtils.SEARCH_AUTH_POLICY_NAME);
511
512       } catch (Exception e) {
513         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processQuery", e.getMessage());
514         return handleError(request, content, HttpStatus.FORBIDDEN);
515       }
516
517       if (!isValid) {
518         return handleError(request, content, HttpStatus.FORBIDDEN);
519       }
520
521       SuggestionStatement suggestionStatement;
522
523       try {
524         // Marshall the supplied request payload into a search statement
525         // object.
526         suggestionStatement = mapper.readValue(content, SuggestionStatement.class);
527
528       } catch (Exception e) {
529         return handleError(request, e.getMessage(), HttpStatus.BAD_REQUEST);
530       }
531
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);
539       } else {
540         output = result.getError() != null
541                 ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
542                 : result.getFailureCause();
543       }
544       ResponseEntity<String> response = ResponseEntity.status(result.getResultCode()).body(output);
545
546       // Clear the MDC context so that no other transaction inadvertently
547       // uses our transaction id.
548       ApiUtils.clearMdcContext();
549
550       return response;
551
552     } catch (Exception e) {
553       return handleError(request, e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
554     }
555   }
556   
557   /**
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.
561    * 
562    * @param headers - The HTTP headers to examine.
563    * 
564    * @return - true if the headers indicate that missing indices should be implicitly created,
565    *           false otherwise.
566    */
567   private boolean implicitlyCreateIndex(HttpHeaders headers) {
568     
569     boolean createIndexIfNotPresent = false;
570     String implicitIndexCreationHeader = 
571         headers.getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
572     
573     if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
574       createIndexIfNotPresent = true;
575     }
576     
577     return createIndexIfNotPresent;
578   }
579
580   private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
581           throws JsonProcessingException {
582     StringBuffer output = new StringBuffer();
583     output.append("{\r\n\"searchResult\":");
584     output.append(
585             mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSearchResult()));
586     AggregationResults aggs = result.getAggregationResult();
587     if (aggs != null) {
588       output.append(",\r\n\"aggregationResult\":");
589       output.append(mapper.setSerializationInclusion(Include.NON_NULL)
590               .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
591     }
592     output.append("\r\n}");
593     return output.toString();
594   }
595
596   private String prepareSuggestOutput(ObjectMapper mapper, SearchOperationResult result)
597           throws JsonProcessingException {
598     StringBuffer output = new StringBuffer();
599     output.append("{\r\n\"searchResult\":");
600     output.append(
601             mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSuggestResult()));
602     AggregationResults aggs = result.getAggregationResult();
603     if (aggs != null) {
604       output.append(",\r\n\"aggregationResult\":");
605       output.append(mapper.setSerializationInclusion(Include.NON_NULL)
606               .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
607     }
608     output.append("\r\n}");
609     return output.toString();
610   }
611
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);
615   }
616
617   void logResult(HttpServletRequest request, HttpStatus status) {
618
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 ()));
622
623     auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
624         new LogFields()
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()));
630
631     // Clear the MDC context so that no other transaction inadvertently
632     // uses our transaction id.
633     ApiUtils.clearMdcContext();
634   }
635 }