63109ef0fce65be6c5db9dd7cd4663f656062b05
[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
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;
39
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;
46
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";
51
52   protected SearchServiceApi searchService = null;
53
54   private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
55   private Logger auditLogger =
56       LoggerFactory.getInstance().getAuditLogger(DocumentApi.class.getName());
57
58   public DocumentApi(SearchServiceApi searchService) {
59     this.searchService = searchService;
60   }
61
62   public Response processPost(String content, HttpServletRequest request, HttpHeaders headers,
63       HttpServletResponse httpResponse, String index, DocumentStoreInterface documentStore) {
64
65     // Initialize the MDC Context for logging purposes.
66     ApiUtils.initMdcContext(request, headers);
67
68     try {
69       ObjectMapper mapper = new ObjectMapper();
70       mapper.setSerializationInclusion(Include.NON_EMPTY);
71       if (content == null) {
72         return handleError(request, content, Status.BAD_REQUEST);
73       }
74
75       boolean isValid;
76       try {
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",
81             e.getMessage());
82         return handleError(request, content, Status.FORBIDDEN);
83       }
84
85       if (!isValid) {
86         return handleError(request, content, Status.FORBIDDEN);
87       }
88
89       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
90       document.setContent(content);
91
92       DocumentOperationResult result =
93           documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
94       String output = null;
95       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
96         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
97       } else {
98         output = result.getError() != null
99             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
100             : result.getFailureCause();
101       }
102
103       if (httpResponse != null) {
104         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
105       }
106       Response response = Response.status(result.getResultCode()).entity(output).build();
107       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
108
109       // Clear the MDC context so that no other transaction inadvertently
110       // uses our transaction id.
111       ApiUtils.clearMdcContext();
112
113       return response;
114     } catch (Exception e) {
115       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
116     }
117   }
118
119   public Response processPut(String content, HttpServletRequest request, HttpHeaders headers,
120       HttpServletResponse httpResponse, String index, String id,
121       DocumentStoreInterface documentStore) {
122
123     // Initialize the MDC Context for logging purposes.
124     ApiUtils.initMdcContext(request, headers);
125
126     try {
127       ObjectMapper mapper = new ObjectMapper();
128       mapper.setSerializationInclusion(Include.NON_EMPTY);
129       if (content == null) {
130         return handleError(request, content, Status.BAD_REQUEST);
131       }
132
133       boolean isValid;
134       try {
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",
139             e.getMessage());
140         return handleError(request, content, Status.FORBIDDEN);
141       }
142
143       if (!isValid) {
144         return handleError(request, content, Status.FORBIDDEN);
145       }
146
147       String resourceVersion =
148           headers.getRequestHeaders().getFirst(REQUEST_HEADER_RESOURCE_VERSION);
149
150       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
151       document.setId(id);
152       document.setContent(content);
153       document.setVersion(resourceVersion);
154
155       DocumentOperationResult result = null;
156       if (resourceVersion == null) {
157         result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
158       } else {
159         result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
160       }
161
162       String output = null;
163       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
164         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
165       } else {
166         output = result.getError() != null
167             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
168             : result.getFailureCause();
169       }
170       if (httpResponse != null) {
171         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
172       }
173       Response response = Response.status(result.getResultCode()).entity(output).build();
174       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
175
176       // Clear the MDC context so that no other transaction inadvertently
177       // uses our transaction id.
178       ApiUtils.clearMdcContext();
179
180       return response;
181     } catch (Exception e) {
182       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
183     }
184   }
185
186   public Response processDelete(String content, HttpServletRequest request, HttpHeaders headers,
187       HttpServletResponse httpResponse, String index, String id,
188       DocumentStoreInterface documentStore) {
189
190     // Initialize the MDC Context for logging purposes.
191     ApiUtils.initMdcContext(request, headers);
192
193     try {
194       ObjectMapper mapper = new ObjectMapper();
195       mapper.setSerializationInclusion(Include.NON_EMPTY);
196       boolean isValid;
197       try {
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",
202             e.getMessage());
203         return handleError(request, content, Status.FORBIDDEN);
204       }
205
206       if (!isValid) {
207         return handleError(request, content, Status.FORBIDDEN);
208       }
209
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);
215       }
216
217       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
218       document.setId(id);
219       document.setVersion(resourceVersion);
220
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();
227       }
228
229       if (httpResponse != null) {
230         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
231       }
232       Response response;
233       if (output == null) {
234         response = Response.status(result.getResultCode()).build();
235       } else {
236         response = Response.status(result.getResultCode()).entity(output).build();
237       }
238
239       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
240
241       // Clear the MDC context so that no other transaction inadvertently
242       // uses our transaction id.
243       ApiUtils.clearMdcContext();
244
245       return response;
246     } catch (Exception e) {
247       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
248     }
249   }
250
251   public Response processGet(String content, HttpServletRequest request, HttpHeaders headers,
252       HttpServletResponse httpResponse, String index, String id,
253       DocumentStoreInterface documentStore) {
254
255     // Initialize the MDC Context for logging purposes.
256     ApiUtils.initMdcContext(request, headers);
257
258     try {
259       ObjectMapper mapper = new ObjectMapper();
260       mapper.setSerializationInclusion(Include.NON_EMPTY);
261       boolean isValid;
262       try {
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",
267             e.getMessage());
268         return handleError(request, content, Status.FORBIDDEN);
269       }
270
271       if (!isValid) {
272         return handleError(request, content, Status.FORBIDDEN);
273       }
274
275       String resourceVersion =
276           headers.getRequestHeaders().getFirst(REQUEST_HEADER_RESOURCE_VERSION);
277
278       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
279       document.setId(id);
280       document.setVersion(resourceVersion);
281
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());
286       } else {
287         output = result.getError() != null
288             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
289             : result.getFailureCause();
290       }
291       if (httpResponse != null) {
292         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
293       }
294       Response response = Response.status(result.getResultCode()).entity(output).build();
295       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
296
297       // Clear the MDC context so that no other transaction inadvertently
298       // uses our transaction id.
299       ApiUtils.clearMdcContext();
300
301       return response;
302     } catch (Exception e) {
303       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
304     }
305   }
306
307   public Response processSearchWithGet(String content, HttpServletRequest request,
308       HttpHeaders headers, String index, String queryText, DocumentStoreInterface documentStore) {
309
310     // Initialize the MDC Context for logging purposes.
311     ApiUtils.initMdcContext(request, headers);
312
313     try {
314       ObjectMapper mapper = new ObjectMapper();
315       mapper.setSerializationInclusion(Include.NON_EMPTY);
316
317       boolean isValid;
318       try {
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",
323             e.getMessage());
324         return handleError(request, content, Status.FORBIDDEN);
325       }
326
327       if (!isValid) {
328         return handleError(request, content, Status.FORBIDDEN);
329       }
330
331       SearchOperationResult result = documentStore.search(index, queryText);
332       String output = null;
333       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
334         output =
335             mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSearchResult());
336       } else {
337         output = result.getError() != null
338             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
339             : result.getFailureCause();
340       }
341       Response response = Response.status(result.getResultCode()).entity(output).build();
342
343       // Clear the MDC context so that no other transaction inadvertently
344       // uses our transaction id.
345       ApiUtils.clearMdcContext();
346
347       return response;
348     } catch (Exception e) {
349       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
350     }
351   }
352
353   public Response queryWithGetWithPayload(String content, HttpServletRequest request,
354       HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
355
356     // Initialize the MDC Context for logging purposes.
357     ApiUtils.initMdcContext(request, headers);
358
359     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET",
360         (request != null) ? request.getRequestURL().toString() : "");
361     if (logger.isDebugEnabled()) {
362       logger.debug("Request Body: " + content);
363     }
364     return processQuery(index, content, request, headers, documentStore);
365   }
366
367   public Response processSearchWithPost(String content, HttpServletRequest request,
368       HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
369
370     // Initialize the MDC Context for logging purposes.
371     ApiUtils.initMdcContext(request, headers);
372
373     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST",
374         (request != null) ? request.getRequestURL().toString() : "");
375     if (logger.isDebugEnabled()) {
376       logger.debug("Request Body: " + content);
377     }
378
379     return processQuery(index, content, request, headers, documentStore);
380   }
381
382   public Response processSuggestQueryWithPost(String content, HttpServletRequest request,
383       HttpHeaders headers, String index, DocumentStoreInterface documentStore) {
384
385     // Initialize the MDC Context for logging purposes.
386     ApiUtils.initMdcContext(request, headers);
387
388     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST",
389         (request != null) ? request.getRequestURL().toString() : "");
390     if (logger.isDebugEnabled()) {
391       logger.debug("Request Body: " + content);
392     }
393
394     return processSuggestQuery(index, content, request, headers, documentStore);
395   }
396
397   /**
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.
400    *
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.
406    */
407   private Response processQuery(String index, String content, HttpServletRequest request,
408       HttpHeaders headers, DocumentStoreInterface documentStore) {
409
410     try {
411       ObjectMapper mapper = new ObjectMapper();
412       mapper.setSerializationInclusion(Include.NON_EMPTY);
413
414       // Make sure that we were supplied a payload before proceeding.
415       if (content == null) {
416         return handleError(request, content, Status.BAD_REQUEST);
417       }
418
419       // Validate that the request has the appropriate authorization.
420       boolean isValid;
421       try {
422         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
423             ApiUtils.SEARCH_AUTH_POLICY_NAME);
424
425       } catch (Exception e) {
426         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processQuery", e.getMessage());
427         return handleError(request, content, Status.FORBIDDEN);
428       }
429
430       if (!isValid) {
431         return handleError(request, content, Status.FORBIDDEN);
432       }
433
434       SearchStatement searchStatement;
435
436       try {
437         // Marshall the supplied request payload into a search statement
438         // object.
439         searchStatement = mapper.readValue(content, SearchStatement.class);
440
441       } catch (Exception e) {
442         return handleError(request, e.getMessage(), Status.BAD_REQUEST);
443       }
444
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);
452       } else {
453         output = result.getError() != null
454             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
455             : result.getFailureCause();
456       }
457       Response response = Response.status(result.getResultCode()).entity(output).build();
458
459       // Clear the MDC context so that no other transaction inadvertently
460       // uses our transaction id.
461       ApiUtils.clearMdcContext();
462
463       return response;
464
465     } catch (Exception e) {
466       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
467     }
468   }
469
470   /**
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.
473    *
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.
479    */
480   private Response processSuggestQuery(String index, String content, HttpServletRequest request,
481       HttpHeaders headers, DocumentStoreInterface documentStore) {
482
483     try {
484       ObjectMapper mapper = new ObjectMapper();
485       mapper.setSerializationInclusion(Include.NON_EMPTY);
486
487       // Make sure that we were supplied a payload before proceeding.
488       if (content == null) {
489         return handleError(request, content, Status.BAD_REQUEST);
490       }
491
492       // Validate that the request has the appropriate authorization.
493       boolean isValid;
494       try {
495         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
496             ApiUtils.SEARCH_AUTH_POLICY_NAME);
497
498       } catch (Exception e) {
499         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL, "processQuery", e.getMessage());
500         return handleError(request, content, Status.FORBIDDEN);
501       }
502
503       if (!isValid) {
504         return handleError(request, content, Status.FORBIDDEN);
505       }
506
507       SuggestionStatement suggestionStatement;
508
509       try {
510         // Marshall the supplied request payload into a search statement
511         // object.
512         suggestionStatement = mapper.readValue(content, SuggestionStatement.class);
513
514       } catch (Exception e) {
515         return handleError(request, e.getMessage(), Status.BAD_REQUEST);
516       }
517
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);
525       } else {
526         output = result.getError() != null
527             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
528             : result.getFailureCause();
529       }
530       Response response = Response.status(result.getResultCode()).entity(output).build();
531
532       // Clear the MDC context so that no other transaction inadvertently
533       // uses our transaction id.
534       ApiUtils.clearMdcContext();
535
536       return response;
537
538     } catch (Exception e) {
539       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
540     }
541   }
542
543   /**
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
546    * in the data store.
547    * 
548    * @param headers - The HTTP headers to examine.
549    * 
550    * @return - true if the headers indicate that missing indices should be implicitly created, false
551    *         otherwise.
552    */
553   private boolean implicitlyCreateIndex(HttpHeaders headers) {
554
555     boolean createIndexIfNotPresent = false;
556     String implicitIndexCreationHeader =
557         headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
558
559     if ((implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true"))) {
560       createIndexIfNotPresent = true;
561     }
562
563     return createIndexIfNotPresent;
564   }
565
566   private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
567       throws JsonProcessingException {
568     StringBuffer output = new StringBuffer();
569     output.append("{\r\n\"searchResult\":");
570     output.append(
571         mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSearchResult()));
572     AggregationResults aggs = result.getAggregationResult();
573     if (aggs != null) {
574       output.append(",\r\n\"aggregationResult\":");
575       output.append(mapper.setSerializationInclusion(Include.NON_NULL)
576           .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
577     }
578     output.append("\r\n}");
579     return output.toString();
580   }
581
582   private String prepareSuggestOutput(ObjectMapper mapper, SearchOperationResult result)
583       throws JsonProcessingException {
584     StringBuffer output = new StringBuffer();
585     output.append("{\r\n\"searchResult\":");
586     output.append(
587         mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getSuggestResult()));
588     AggregationResults aggs = result.getAggregationResult();
589     if (aggs != null) {
590       output.append(",\r\n\"aggregationResult\":");
591       output.append(mapper.setSerializationInclusion(Include.NON_NULL)
592           .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
593     }
594     output.append("\r\n}");
595     return output.toString();
596   }
597
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();
601   }
602
603   void logResult(HttpServletRequest request, Response.Status status) {
604
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()));
608
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()));
615
616     // Clear the MDC context so that no other transaction inadvertently
617     // uses our transaction id.
618     ApiUtils.clearMdcContext();
619   }
620 }