001/*
002 * Copyright 2010-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2010-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2010-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.listener;
037
038
039
040import java.net.Socket;
041import java.text.DecimalFormat;
042import java.text.SimpleDateFormat;
043import java.util.Date;
044import java.util.List;
045import java.util.concurrent.ConcurrentHashMap;
046import java.util.concurrent.atomic.AtomicLong;
047import java.util.logging.Handler;
048import java.util.logging.Level;
049import java.util.logging.LogRecord;
050
051import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
052import com.unboundid.ldap.protocol.AddRequestProtocolOp;
053import com.unboundid.ldap.protocol.AddResponseProtocolOp;
054import com.unboundid.ldap.protocol.BindRequestProtocolOp;
055import com.unboundid.ldap.protocol.BindResponseProtocolOp;
056import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
057import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
058import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
059import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
060import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
061import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
062import com.unboundid.ldap.protocol.LDAPMessage;
063import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
064import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
065import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
066import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
067import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
068import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
069import com.unboundid.ldap.protocol.SearchResultEntryProtocolOp;
070import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
071import com.unboundid.ldap.sdk.Control;
072import com.unboundid.ldap.sdk.LDAPException;
073import com.unboundid.ldap.sdk.ResultCode;
074import com.unboundid.util.NotMutable;
075import com.unboundid.util.ObjectPair;
076import com.unboundid.util.StaticUtils;
077import com.unboundid.util.ThreadSafety;
078import com.unboundid.util.ThreadSafetyLevel;
079import com.unboundid.util.Validator;
080import com.unboundid.util.json.JSONBuffer;
081
082
083
084/**
085 * This class provides a request handler that may be used to log each request
086 * and result using the Java logging framework.  Messages will be formatted as
087 * JSON objects.  It will be also be associated with another request handler
088 * that will actually be used to handle the request.
089 */
090@NotMutable()
091@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
092public final class JSONAccessLogRequestHandler
093       extends LDAPListenerRequestHandler
094       implements SearchEntryTransformer
095{
096  // The operation ID counter that will be used for this request handler
097  // instance.
098  private final AtomicLong nextOperationID;
099
100  // A map used to correlate the number of search result entries returned for a
101  // particular message ID.
102  private final ConcurrentHashMap<Integer,AtomicLong> entryCounts;
103
104  // The log handler that will be used to log the messages.
105  private final Handler logHandler;
106
107  // The client connection with which this request handler is associated.
108  private final LDAPListenerClientConnection clientConnection;
109
110  // The request handler that actually will be used to process any requests
111  // received.
112  private final LDAPListenerRequestHandler requestHandler;
113
114  // The thread-local decimal formatters that will be used to format etime
115  // values.
116  private final ThreadLocal<DecimalFormat> decimalFormatters;
117
118  // The thread-local JSON buffers that will be used to format log message
119  // objects.
120  private final ThreadLocal<JSONBuffer> jsonBuffers;
121
122  // The thread-local date formatters that will be used to format timestamps.
123  private final ThreadLocal<SimpleDateFormat> timestampFormatters;
124
125
126
127  /**
128   * Creates a new JSON-formatted access log request handler that will log
129   * request and result messages using the provided log handler, and will
130   * process client requests using the provided request handler.
131   *
132   * @param  logHandler      The log handler that will be used to log request
133   *                         and result messages.  Note that all messages will
134   *                         be logged at the INFO level.  It must not be
135   *                         {@code null}.  Note that the log handler will not
136   *                         be automatically closed when the associated
137   *                         listener is shut down.
138   * @param  requestHandler  The request handler that will actually be used to
139   *                         process any requests received.  It must not be
140   *                         {@code null}.
141   */
142  public JSONAccessLogRequestHandler(final Handler logHandler,
143              final LDAPListenerRequestHandler requestHandler)
144  {
145    Validator.ensureNotNull(logHandler, requestHandler);
146
147    this.logHandler = logHandler;
148    this.requestHandler = requestHandler;
149
150    nextOperationID = null;
151    clientConnection = null;
152    entryCounts = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(50));
153    jsonBuffers = new ThreadLocal<>();
154    timestampFormatters = new ThreadLocal<>();
155    decimalFormatters = new ThreadLocal<>();
156  }
157
158
159
160  /**
161   * Creates a new JSON-formatted access log request handler that will log
162   * request and result messages using the provided log handler, and will
163   * process client requests using the provided request handler.
164   *
165   * @param  logHandler           The log handler that will be used to log
166   *                              request and result messages.  Note that all
167   *                              messages will be logged at the INFO level.  It
168   *                              must not be {@code null}.
169   * @param  requestHandler       The request handler that will actually be used
170   *                              to process any requests received.  It must not
171   *                              be {@code null}.
172   * @param  clientConnection     The client connection with which this instance
173   *                              is associated.
174   * @param  jsonBuffers          The thread-local JSON buffers that will be
175   *                              used to format log message objects.
176   * @param  timestampFormatters  The thread-local date formatters that will be
177   *                              used to format timestamps.
178   * @param  decimalFormatters    The thread-local decimal formatters that
179   *                              will be used to format etime values.
180   */
181  private JSONAccessLogRequestHandler(final Handler logHandler,
182               final LDAPListenerRequestHandler requestHandler,
183               final LDAPListenerClientConnection clientConnection,
184               final ThreadLocal<JSONBuffer> jsonBuffers,
185               final ThreadLocal<SimpleDateFormat> timestampFormatters,
186               final ThreadLocal<DecimalFormat> decimalFormatters)
187  {
188    this.logHandler = logHandler;
189    this.requestHandler = requestHandler;
190    this.clientConnection = clientConnection;
191    this.jsonBuffers = jsonBuffers;
192    this.timestampFormatters = timestampFormatters;
193    this.decimalFormatters = decimalFormatters;
194
195    nextOperationID = new AtomicLong(0L);
196    entryCounts = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(50));
197  }
198
199
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override()
205  public JSONAccessLogRequestHandler newInstance(
206              final LDAPListenerClientConnection connection)
207         throws LDAPException
208  {
209    final JSONAccessLogRequestHandler h =
210         new JSONAccessLogRequestHandler(logHandler,
211              requestHandler.newInstance(connection), connection, jsonBuffers,
212              timestampFormatters, decimalFormatters);
213    connection.addSearchEntryTransformer(h);
214
215    final JSONBuffer buffer = h.getConnectionHeader("connect");
216
217    final Socket s = connection.getSocket();
218    buffer.appendString("from-address", s.getInetAddress().getHostAddress());
219    buffer.appendNumber("from-port", s.getPort());
220    buffer.appendString("to-address", s.getLocalAddress().getHostAddress());
221    buffer.appendNumber("to-port", s.getLocalPort());
222    buffer.endObject();
223
224    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
225    logHandler.flush();
226
227    return h;
228  }
229
230
231
232  /**
233   * {@inheritDoc}
234   */
235  @Override()
236  public void closeInstance()
237  {
238    final JSONBuffer buffer = getConnectionHeader("disconnect");
239    buffer.endObject();
240
241    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
242    logHandler.flush();
243
244    requestHandler.closeInstance();
245  }
246
247
248
249  /**
250   * {@inheritDoc}
251   */
252  @Override()
253  public void processAbandonRequest(final int messageID,
254                                    final AbandonRequestProtocolOp request,
255                                    final List<Control> controls)
256  {
257    final JSONBuffer buffer = getRequestHeader("abandon",
258         nextOperationID.incrementAndGet(), messageID);
259
260    buffer.appendNumber("id-to-abandon", request.getIDToAbandon());
261    buffer.endObject();
262
263    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
264    logHandler.flush();
265
266    requestHandler.processAbandonRequest(messageID, request, controls);
267  }
268
269
270
271  /**
272   * {@inheritDoc}
273   */
274  @Override()
275  public LDAPMessage processAddRequest(final int messageID,
276                                       final AddRequestProtocolOp request,
277                                       final List<Control> controls)
278  {
279    final long opID = nextOperationID.getAndIncrement();
280    final JSONBuffer buffer = getRequestHeader("add", opID, messageID);
281
282    buffer.appendString("dn", request.getDN());
283    buffer.endObject();
284
285    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
286    logHandler.flush();
287
288    final long startTimeNanos = System.nanoTime();
289    final LDAPMessage responseMessage = requestHandler.processAddRequest(
290         messageID, request, controls);
291    final long eTimeNanos = System.nanoTime() - startTimeNanos;
292    final AddResponseProtocolOp protocolOp =
293         responseMessage.getAddResponseProtocolOp();
294
295    generateResponse(buffer, "add", opID, messageID,
296         protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
297         protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
298    buffer.endObject();
299
300    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
301    logHandler.flush();
302
303    return responseMessage;
304  }
305
306
307
308  /**
309   * {@inheritDoc}
310   */
311  @Override()
312  public LDAPMessage processBindRequest(final int messageID,
313                                        final BindRequestProtocolOp request,
314                                        final List<Control> controls)
315  {
316    final long opID = nextOperationID.getAndIncrement();
317
318    final JSONBuffer buffer = getRequestHeader("bind", opID, messageID);
319    buffer.appendNumber("ldap-version", request.getVersion());
320    buffer.appendString("dn", request.getBindDN());
321
322    switch (request.getCredentialsType())
323    {
324      case BindRequestProtocolOp.CRED_TYPE_SIMPLE:
325        buffer.appendString("authentication-type", "simple");
326        break;
327
328      case BindRequestProtocolOp.CRED_TYPE_SASL:
329        buffer.appendString("authentication-type", "sasl");
330        buffer.appendString("sasl-mechanism", request.getSASLMechanism());
331        break;
332    }
333    buffer.endObject();
334
335    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
336    logHandler.flush();
337
338    final long startTimeNanos = System.nanoTime();
339    final LDAPMessage responseMessage = requestHandler.processBindRequest(
340         messageID, request, controls);
341    final long eTimeNanos = System.nanoTime() - startTimeNanos;
342    final BindResponseProtocolOp protocolOp =
343         responseMessage.getBindResponseProtocolOp();
344
345    generateResponse(buffer, "bind", opID, messageID,
346         protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
347         protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
348    buffer.endObject();
349
350    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
351    logHandler.flush();
352
353    return responseMessage;
354  }
355
356
357
358  /**
359   * {@inheritDoc}
360   */
361  @Override()
362  public LDAPMessage processCompareRequest(final int messageID,
363                          final CompareRequestProtocolOp request,
364                          final List<Control> controls)
365  {
366    final long opID = nextOperationID.getAndIncrement();
367
368    final JSONBuffer buffer = getRequestHeader("compare", opID, messageID);
369    buffer.appendString("dn", request.getDN());
370    buffer.appendString("attribute-type", request.getAttributeName());
371    buffer.endObject();
372
373    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
374    logHandler.flush();
375
376    final long startTimeNanos = System.nanoTime();
377    final LDAPMessage responseMessage = requestHandler.processCompareRequest(
378         messageID, request, controls);
379    final long eTimeNanos = System.nanoTime() - startTimeNanos;
380    final CompareResponseProtocolOp protocolOp =
381         responseMessage.getCompareResponseProtocolOp();
382
383    generateResponse(buffer, "compare", opID, messageID,
384         protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
385         protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
386    buffer.endObject();
387
388    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
389    logHandler.flush();
390
391    return responseMessage;
392  }
393
394
395
396  /**
397   * {@inheritDoc}
398   */
399  @Override()
400  public LDAPMessage processDeleteRequest(final int messageID,
401                                          final DeleteRequestProtocolOp request,
402                                          final List<Control> controls)
403  {
404    final long opID = nextOperationID.getAndIncrement();
405
406    final JSONBuffer buffer = getRequestHeader("delete", opID, messageID);
407    buffer.appendString("dn", request.getDN());
408    buffer.endObject();
409
410    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
411    logHandler.flush();
412
413    final long startTimeNanos = System.nanoTime();
414    final LDAPMessage responseMessage = requestHandler.processDeleteRequest(
415         messageID, request, controls);
416    final long eTimeNanos = System.nanoTime() - startTimeNanos;
417    final DeleteResponseProtocolOp protocolOp =
418         responseMessage.getDeleteResponseProtocolOp();
419
420    generateResponse(buffer, "delete", opID, messageID,
421         protocolOp.getResultCode(),
422         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
423         protocolOp.getReferralURLs(), eTimeNanos);
424    buffer.endObject();
425
426    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
427    logHandler.flush();
428
429    return responseMessage;
430  }
431
432
433
434  /**
435   * {@inheritDoc}
436   */
437  @Override()
438  public LDAPMessage processExtendedRequest(final int messageID,
439                          final ExtendedRequestProtocolOp request,
440                          final List<Control> controls)
441  {
442    final long opID = nextOperationID.getAndIncrement();
443
444    final JSONBuffer buffer = getRequestHeader("extended", opID, messageID);
445    buffer.appendString("request-oid", request.getOID());
446    buffer.endObject();
447
448    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
449    logHandler.flush();
450
451    final long startTimeNanos = System.nanoTime();
452    final LDAPMessage responseMessage = requestHandler.processExtendedRequest(
453         messageID, request, controls);
454    final long eTimeNanos = System.nanoTime() - startTimeNanos;
455    final ExtendedResponseProtocolOp protocolOp =
456         responseMessage.getExtendedResponseProtocolOp();
457
458    generateResponse(buffer, "extended", opID, messageID,
459         protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
460         protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
461
462    final String responseOID = protocolOp.getResponseOID();
463    if (responseOID != null)
464    {
465      buffer.appendString("response-oid", responseOID);
466    }
467
468    buffer.endObject();
469
470    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
471    logHandler.flush();
472
473    return responseMessage;
474  }
475
476
477
478  /**
479   * {@inheritDoc}
480   */
481  @Override()
482  public LDAPMessage processModifyRequest(final int messageID,
483                                          final ModifyRequestProtocolOp request,
484                                          final List<Control> controls)
485  {
486    final long opID = nextOperationID.getAndIncrement();
487
488    final JSONBuffer buffer = getRequestHeader("modify", opID, messageID);
489    buffer.appendString("dn", request.getDN());
490    buffer.endObject();
491
492    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
493    logHandler.flush();
494
495    final long startTimeNanos = System.nanoTime();
496    final LDAPMessage responseMessage = requestHandler.processModifyRequest(
497         messageID, request, controls);
498    final long eTimeNanos = System.nanoTime() - startTimeNanos;
499    final ModifyResponseProtocolOp protocolOp =
500         responseMessage.getModifyResponseProtocolOp();
501
502    generateResponse(buffer, "modify", opID, messageID,
503         protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
504         protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
505    buffer.endObject();
506
507    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
508    logHandler.flush();
509
510    return responseMessage;
511  }
512
513
514
515  /**
516   * {@inheritDoc}
517   */
518  @Override()
519  public LDAPMessage processModifyDNRequest(final int messageID,
520                          final ModifyDNRequestProtocolOp request,
521                          final List<Control> controls)
522  {
523    final long opID = nextOperationID.getAndIncrement();
524
525    final JSONBuffer buffer = getRequestHeader("modify-dn", opID, messageID);
526    buffer.appendString("dn", request.getDN());
527    buffer.appendString("new-rdn", request.getNewRDN());
528    buffer.appendBoolean("delete-old-rdn", request.deleteOldRDN());
529
530    final String newSuperior = request.getNewSuperiorDN();
531    if (newSuperior != null)
532    {
533      buffer.appendString("new-superior", newSuperior);
534    }
535    buffer.endObject();
536
537    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
538    logHandler.flush();
539
540    final long startTimeNanos = System.nanoTime();
541    final LDAPMessage responseMessage = requestHandler.processModifyDNRequest(
542         messageID, request, controls);
543    final long eTimeNanos = System.nanoTime() - startTimeNanos;
544    final ModifyDNResponseProtocolOp protocolOp =
545         responseMessage.getModifyDNResponseProtocolOp();
546
547    generateResponse(buffer, "modify-dn", opID, messageID,
548         protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
549         protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
550    buffer.endObject();
551
552    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
553    logHandler.flush();
554
555    return responseMessage;
556  }
557
558
559
560  /**
561   * {@inheritDoc}
562   */
563  @Override()
564  public LDAPMessage processSearchRequest(final int messageID,
565                                          final SearchRequestProtocolOp request,
566                                          final List<Control> controls)
567  {
568    final long opID = nextOperationID.getAndIncrement();
569
570    final JSONBuffer buffer = getRequestHeader("search", opID, messageID);
571    buffer.appendString("base", request.getBaseDN());
572    buffer.appendNumber("scope", request.getScope().intValue());
573    buffer.appendString("filter", request.getFilter().toString());
574
575    buffer.beginArray("requested-attributes");
576    for (final String requestedAttribute : request.getAttributes())
577    {
578      buffer.appendString(requestedAttribute);
579    }
580    buffer.endArray();
581    buffer.endObject();
582
583    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
584    logHandler.flush();
585
586    final AtomicLong entryCounter = new AtomicLong(0L);
587    entryCounts.put(messageID, entryCounter);
588
589    try
590    {
591      final long startTimeNanos = System.nanoTime();
592      final LDAPMessage responseMessage = requestHandler.processSearchRequest(
593           messageID, request, controls);
594      final long eTimeNanos = System.nanoTime() - startTimeNanos;
595      final SearchResultDoneProtocolOp protocolOp =
596           responseMessage.getSearchResultDoneProtocolOp();
597
598      generateResponse(buffer, "search", opID, messageID,
599           protocolOp.getResultCode(), protocolOp.getDiagnosticMessage(),
600           protocolOp.getMatchedDN(), protocolOp.getReferralURLs(), eTimeNanos);
601      buffer.appendNumber("entries-returned", entryCounter.get());
602      buffer.endObject();
603
604      logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
605      logHandler.flush();
606
607      return responseMessage;
608    }
609    finally
610    {
611      entryCounts.remove(messageID);
612    }
613  }
614
615
616
617  /**
618   * {@inheritDoc}
619   */
620  @Override()
621  public void processUnbindRequest(final int messageID,
622                                   final UnbindRequestProtocolOp request,
623                                   final List<Control> controls)
624  {
625    final JSONBuffer buffer = getRequestHeader("unbind",
626         nextOperationID.getAndIncrement(), messageID);
627    buffer.endObject();
628
629    logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
630    logHandler.flush();
631
632    requestHandler.processUnbindRequest(messageID, request, controls);
633  }
634
635
636
637  /**
638   * Retrieves a JSON buffer that can be used to construct a log message.
639   *
640   * @return  A JSON buffer that can be used to construct a log message.
641   */
642  private JSONBuffer getBuffer()
643  {
644    JSONBuffer buffer = jsonBuffers.get();
645    if (buffer == null)
646    {
647      buffer = new JSONBuffer();
648      jsonBuffers.set(buffer);
649    }
650    else
651    {
652      buffer.clear();
653    }
654
655    return buffer;
656  }
657
658
659
660  /**
661   * Adds a timestamp to the provided buffer.
662   *
663   * @param  buffer  The buffer to which the timestamp should be added.
664   */
665  private void addTimestamp(final JSONBuffer buffer)
666  {
667    SimpleDateFormat timestampFormatter = timestampFormatters.get();
668    if (timestampFormatter == null)
669    {
670      timestampFormatter =
671           new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'");
672      timestampFormatter.setTimeZone(StaticUtils.getUTCTimeZone());
673      timestampFormatters.set(timestampFormatter);
674    }
675
676    buffer.appendString("timestamp", timestampFormatter.format(new Date()));
677  }
678
679
680
681  /**
682   * Retrieves a {@code JSONBuffer} with header information for a connect log
683   * message for the specified type of operation.
684   *
685   * @param  messageType  The type of operation being requested.
686   *
687   * @return  A {@code JSONBuffer} with header information appended for the
688   *          connection;
689   */
690  private JSONBuffer getConnectionHeader(final String messageType)
691  {
692    final JSONBuffer buffer = getBuffer();
693    buffer.beginObject();
694    addTimestamp(buffer);
695    buffer.appendString("message-type", messageType);
696    buffer.appendNumber("connection-id", clientConnection.getConnectionID());
697
698    return buffer;
699  }
700
701
702
703  /**
704   * Retrieves a {@code JSONBuffer} with header information for a request log
705   * message for the specified type of operation.
706   *
707   * @param  opType  The type of operation being requested.
708   * @param  opID    The operation ID for the request.
709   * @param  msgID   The message ID for the request.
710   *
711   * @return  A {@code StringBuilder} with header information appended for the
712   *          request;
713   */
714  private JSONBuffer getRequestHeader(final String opType, final long opID,
715                                      final int msgID)
716  {
717    final JSONBuffer buffer = getBuffer();
718    buffer.beginObject();
719    addTimestamp(buffer);
720    buffer.appendString("message-type", "request");
721    buffer.appendString("operation-type", opType);
722    buffer.appendNumber("connection-id", clientConnection.getConnectionID());
723    buffer.appendNumber("operation-id", opID);
724    buffer.appendNumber("message-id", msgID);
725
726    return buffer;
727  }
728
729
730
731  /**
732   * Updates the provided JSON buffer with information about the result of
733   * processing an operation.
734   *
735   * @param  buffer             The buffer to which the information will be
736   *                            written.  It will be cleared before adding any
737   *                            content.
738   * @param  opType             The type of operation that was processed.
739   * @param  opID               The operation ID for the response.
740   * @param  msgID              The message ID for the response.
741   * @param  resultCode         The result code for the response, if any.
742   * @param  diagnosticMessage  The diagnostic message for the response, if any.
743   * @param  matchedDN          The matched DN for the response, if any.
744   * @param  referralURLs       The referral URLs for the response, if any.
745   * @param  eTimeNanos         The length of time in nanoseconds required to
746   *                            process the operation.
747   */
748  private void generateResponse(final JSONBuffer buffer, final String opType,
749                                final long opID, final int msgID,
750                                final int resultCode,
751                                final String diagnosticMessage,
752                                final String matchedDN,
753                                final List<String> referralURLs,
754                                final long eTimeNanos)
755  {
756    buffer.clear();
757
758    buffer.beginObject();
759    addTimestamp(buffer);
760    buffer.appendString("message-type", "response");
761    buffer.appendString("operation-type", opType);
762    buffer.appendNumber("connection-id", clientConnection.getConnectionID());
763    buffer.appendNumber("operation-id", opID);
764    buffer.appendNumber("message-id", msgID);
765    buffer.appendNumber("result-code-value", resultCode);
766
767    final ResultCode rc = ResultCode.valueOf(resultCode, null, false);
768    if (rc != null)
769    {
770      buffer.appendString("result-code-name", rc.getName());
771    }
772
773    if (diagnosticMessage != null)
774    {
775      buffer.appendString("diagnostic-message", diagnosticMessage);
776    }
777
778    if (matchedDN != null)
779    {
780      buffer.appendString("matched-dn", matchedDN);
781    }
782
783    if (! referralURLs.isEmpty())
784    {
785      buffer.beginArray("referral-urls");
786      for (final String url : referralURLs)
787      {
788        buffer.appendString(url);
789      }
790      buffer.endArray();
791    }
792
793    DecimalFormat decimalFormat = decimalFormatters.get();
794    if (decimalFormat == null)
795    {
796      decimalFormat = new DecimalFormat("0.000");
797      decimalFormatters.set(decimalFormat);
798    }
799    final double eTimeMillis = eTimeNanos / 1_000_000.0d;
800    buffer.appendNumber("processing-time-millis",
801         decimalFormat.format(eTimeMillis));
802  }
803
804
805
806  /**
807   * {@inheritDoc}
808   */
809  @Override()
810  public ObjectPair<SearchResultEntryProtocolOp,Control[]> transformEntry(
811              final int messageID, final SearchResultEntryProtocolOp entry,
812              final Control[] controls)
813  {
814    final AtomicLong l = entryCounts.get(messageID);
815    if (l != null)
816    {
817      l.incrementAndGet();
818    }
819
820    return new ObjectPair<>(entry, controls);
821  }
822}