001/*
002 * Copyright 2010-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2010-2024 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-2024 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.Iterator;
045import java.util.List;
046import java.util.concurrent.ConcurrentHashMap;
047import java.util.concurrent.atomic.AtomicLong;
048import java.util.logging.Handler;
049import java.util.logging.Level;
050import java.util.logging.LogRecord;
051
052import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
053import com.unboundid.ldap.protocol.AddRequestProtocolOp;
054import com.unboundid.ldap.protocol.AddResponseProtocolOp;
055import com.unboundid.ldap.protocol.BindRequestProtocolOp;
056import com.unboundid.ldap.protocol.BindResponseProtocolOp;
057import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
058import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
059import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
060import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
061import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
062import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
063import com.unboundid.ldap.protocol.LDAPMessage;
064import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
065import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
066import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
067import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
068import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
069import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
070import com.unboundid.ldap.protocol.SearchResultEntryProtocolOp;
071import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
072import com.unboundid.ldap.sdk.Control;
073import com.unboundid.ldap.sdk.LDAPException;
074import com.unboundid.util.NotMutable;
075import com.unboundid.util.NotNull;
076import com.unboundid.util.Nullable;
077import com.unboundid.util.ObjectPair;
078import com.unboundid.util.StaticUtils;
079import com.unboundid.util.ThreadSafety;
080import com.unboundid.util.ThreadSafetyLevel;
081import com.unboundid.util.Validator;
082
083
084
085/**
086 * This class provides a request handler that may be used to log each request
087 * and result using the Java logging framework.  It will be also be associated
088 * with another request handler that will actually be used to handle the
089 * request.
090 */
091@NotMutable()
092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093public final class AccessLogRequestHandler
094       extends LDAPListenerRequestHandler
095       implements SearchEntryTransformer
096{
097  // The operation ID counter that will be used for this request handler
098  // instance.
099  @Nullable private final AtomicLong nextOperationID;
100
101  // A map used to correlate the number of search result entries returned for a
102  // particular message ID.
103  @NotNull private final ConcurrentHashMap<Integer,AtomicLong> entryCounts =
104       new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(50));
105
106  // The log handler that will be used to log the messages.
107  @NotNull private final Handler logHandler;
108
109  // The client connection with which this request handler is associated.
110  @Nullable private final LDAPListenerClientConnection clientConnection;
111
112  // The request handler that actually will be used to process any requests
113  // received.
114  @NotNull private final LDAPListenerRequestHandler requestHandler;
115
116  // The thread-local decimal formatters that will be used to format etime
117  // values.
118  @NotNull private final ThreadLocal<DecimalFormat> decimalFormatters;
119
120  // The thread-local date formatters that will be used to format timestamps.
121  @NotNull private final ThreadLocal<SimpleDateFormat> timestampFormatters;
122
123  // The thread-local string builders that will be used to build log messages.
124  @NotNull private final ThreadLocal<StringBuilder> buffers;
125
126
127
128  /**
129   * Creates a new access log request handler that will log request and result
130   * messages using the provided log handler, and will process client requests
131   * using the provided request handler.
132   *
133   * @param  logHandler      The log handler that will be used to log request
134   *                         and result messages.  Note that all messages will
135   *                         be logged at the INFO level.  It must not be
136   *                         {@code null}.  Note that the log handler will not
137   *                         be automatically closed when the associated
138   *                         listener is shut down.
139   * @param  requestHandler  The request handler that will actually be used to
140   *                         process any requests received.  It must not be
141   *                         {@code null}.
142   */
143  public AccessLogRequestHandler(@NotNull final Handler logHandler,
144              @NotNull final LDAPListenerRequestHandler requestHandler)
145  {
146    Validator.ensureNotNull(logHandler, requestHandler);
147
148    this.logHandler = logHandler;
149    this.requestHandler = requestHandler;
150
151    decimalFormatters = new ThreadLocal<>();
152    timestampFormatters = new ThreadLocal<>();
153    buffers = new ThreadLocal<>();
154
155    nextOperationID = null;
156    clientConnection = null;
157  }
158
159
160
161  /**
162   * Creates a new access log request handler that will log request and result
163   * messages using the provided log handler, and will process client requests
164   * using the provided request handler.
165   *
166   * @param  logHandler        The log handler that will be used to log request
167   *                           and result messages.  Note that all messages will
168   *                           be logged at the INFO level.  It must not be
169   *                           {@code null}.
170   * @param  requestHandler    The request handler that will actually be used to
171   *                           process any requests received.  It must not be
172   *                           {@code null}.
173   * @param  clientConnection  The client connection with which this instance is
174   *                           associated.
175   * @param  buffers              The thread-local string builders that will be
176   *                              used to build log messages.
177   * @param  timestampFormatters  The thread-local date formatters that will be
178   *                              used to format timestamps.
179   * @param  decimalFormatters    The thread-local decimal formatters that
180   *                              will be used to format etime values.
181   */
182  private AccessLogRequestHandler(@NotNull final Handler logHandler,
183               @NotNull final LDAPListenerRequestHandler requestHandler,
184               @NotNull final LDAPListenerClientConnection clientConnection,
185               @NotNull final ThreadLocal<StringBuilder> buffers,
186               @NotNull final ThreadLocal<SimpleDateFormat> timestampFormatters,
187               @NotNull final ThreadLocal<DecimalFormat> decimalFormatters)
188  {
189    this.logHandler = logHandler;
190    this.requestHandler  = requestHandler;
191    this.clientConnection = clientConnection;
192    this.buffers = buffers;
193    this.timestampFormatters = timestampFormatters;
194    this.decimalFormatters = decimalFormatters;
195
196    nextOperationID  = new AtomicLong(0L);
197  }
198
199
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override()
205  @NotNull()
206  public AccessLogRequestHandler newInstance(
207              @NotNull final LDAPListenerClientConnection connection)
208         throws LDAPException
209  {
210    final AccessLogRequestHandler h = new AccessLogRequestHandler(logHandler,
211         requestHandler.newInstance(connection), connection, buffers,
212         timestampFormatters, decimalFormatters);
213    connection.addSearchEntryTransformer(h);
214
215    final StringBuilder b = h.getConnectionHeader("CONNECT");
216
217    final Socket s = connection.getSocket();
218    b.append(" from=\"");
219    b.append(s.getInetAddress().getHostAddress());
220    b.append(':');
221    b.append(s.getPort());
222    b.append("\" to=\"");
223    b.append(s.getLocalAddress().getHostAddress());
224    b.append(':');
225    b.append(s.getLocalPort());
226    b.append('"');
227
228    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
229    logHandler.flush();
230
231    return h;
232  }
233
234
235
236  /**
237   * {@inheritDoc}
238   */
239  @Override()
240  public void closeInstance()
241  {
242    final StringBuilder b = getConnectionHeader("DISCONNECT");
243    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
244    logHandler.flush();
245
246    requestHandler.closeInstance();
247  }
248
249
250
251  /**
252   * {@inheritDoc}
253   */
254  @Override()
255  public void processAbandonRequest(final int messageID,
256                   @NotNull final AbandonRequestProtocolOp request,
257                   @NotNull final List<Control> controls)
258  {
259    final StringBuilder b = getRequestHeader("ABANDON",
260         nextOperationID.getAndIncrement(), messageID);
261
262    b.append(" idToAbandon=");
263    b.append(request.getIDToAbandon());
264
265    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
266    logHandler.flush();
267
268    requestHandler.processAbandonRequest(messageID, request, controls);
269  }
270
271
272
273  /**
274   * {@inheritDoc}
275   */
276  @Override()
277  @NotNull()
278  public LDAPMessage processAddRequest(final int messageID,
279                          @NotNull final AddRequestProtocolOp request,
280                          @NotNull final List<Control> controls)
281  {
282    final long opID = nextOperationID.getAndIncrement();
283
284    final StringBuilder b = getRequestHeader("ADD", opID, messageID);
285
286    b.append(" dn=\"");
287    b.append(request.getDN());
288    b.append('"');
289
290    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
291    logHandler.flush();
292
293    final long startTimeNanos = System.nanoTime();
294    final LDAPMessage responseMessage = requestHandler.processAddRequest(
295         messageID, request, controls);
296    final long eTimeNanos = System.nanoTime() - startTimeNanos;
297    final AddResponseProtocolOp protocolOp =
298         responseMessage.getAddResponseProtocolOp();
299
300    generateResponse(b, "ADD", opID, messageID, protocolOp.getResultCode(),
301         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
302         protocolOp.getReferralURLs(), eTimeNanos);
303
304    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
305    logHandler.flush();
306
307    return responseMessage;
308  }
309
310
311
312  /**
313   * {@inheritDoc}
314   */
315  @Override()
316  @NotNull()
317  public LDAPMessage processBindRequest(final int messageID,
318                          @NotNull final BindRequestProtocolOp request,
319                          @NotNull final List<Control> controls)
320  {
321    final long opID = nextOperationID.getAndIncrement();
322
323    final StringBuilder b = getRequestHeader("BIND", opID, messageID);
324
325    b.append(" version=");
326    b.append(request.getVersion());
327    b.append(" dn=\"");
328    b.append(request.getBindDN());
329    b.append("\" authType=\"");
330
331    switch (request.getCredentialsType())
332    {
333      case BindRequestProtocolOp.CRED_TYPE_SIMPLE:
334        b.append("SIMPLE");
335        break;
336
337      case BindRequestProtocolOp.CRED_TYPE_SASL:
338        b.append("SASL ");
339        b.append(request.getSASLMechanism());
340        break;
341    }
342
343    b.append('"');
344
345    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
346    logHandler.flush();
347
348    final long startTimeNanos = System.nanoTime();
349    final LDAPMessage responseMessage = requestHandler.processBindRequest(
350         messageID, request, controls);
351    final long eTimeNanos = System.nanoTime() - startTimeNanos;
352    final BindResponseProtocolOp protocolOp =
353         responseMessage.getBindResponseProtocolOp();
354
355    generateResponse(b, "BIND", opID, messageID, protocolOp.getResultCode(),
356         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
357         protocolOp.getReferralURLs(), eTimeNanos);
358
359    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
360    logHandler.flush();
361
362    return responseMessage;
363  }
364
365
366
367  /**
368   * {@inheritDoc}
369   */
370  @Override()
371  @NotNull()
372  public LDAPMessage processCompareRequest(final int messageID,
373                          @NotNull final CompareRequestProtocolOp request,
374                          @NotNull final List<Control> controls)
375  {
376    final long opID = nextOperationID.getAndIncrement();
377
378    final StringBuilder b = getRequestHeader("COMPARE", opID, messageID);
379
380    b.append(" dn=\"");
381    b.append(request.getDN());
382    b.append("\" attr=\"");
383    b.append(request.getAttributeName());
384    b.append('"');
385
386    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
387    logHandler.flush();
388
389    final long startTimeNanos = System.nanoTime();
390    final LDAPMessage responseMessage = requestHandler.processCompareRequest(
391         messageID, request, controls);
392    final long eTimeNanos = System.nanoTime() - startTimeNanos;
393    final CompareResponseProtocolOp protocolOp =
394         responseMessage.getCompareResponseProtocolOp();
395
396    generateResponse(b, "COMPARE", opID, messageID, protocolOp.getResultCode(),
397         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
398         protocolOp.getReferralURLs(), eTimeNanos);
399
400    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
401    logHandler.flush();
402
403    return responseMessage;
404  }
405
406
407
408  /**
409   * {@inheritDoc}
410   */
411  @Override()
412  @NotNull()
413  public LDAPMessage processDeleteRequest(final int messageID,
414                          @NotNull final DeleteRequestProtocolOp request,
415                          @NotNull final List<Control> controls)
416  {
417    final long opID = nextOperationID.getAndIncrement();
418
419    final StringBuilder b = getRequestHeader("DELETE", opID, messageID);
420
421    b.append(" dn=\"");
422    b.append(request.getDN());
423    b.append('"');
424
425    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
426    logHandler.flush();
427
428    final long startTimeNanos = System.nanoTime();
429    final LDAPMessage responseMessage = requestHandler.processDeleteRequest(
430         messageID, request, controls);
431    final long eTimeNanos = System.nanoTime() - startTimeNanos;
432    final DeleteResponseProtocolOp protocolOp =
433         responseMessage.getDeleteResponseProtocolOp();
434
435    generateResponse(b, "DELETE", opID, messageID, protocolOp.getResultCode(),
436         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
437         protocolOp.getReferralURLs(), eTimeNanos);
438
439    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
440    logHandler.flush();
441
442    return responseMessage;
443  }
444
445
446
447  /**
448   * {@inheritDoc}
449   */
450  @Override()
451  @NotNull()
452  public LDAPMessage processExtendedRequest(final int messageID,
453                          @NotNull final ExtendedRequestProtocolOp request,
454                          @NotNull final List<Control> controls)
455  {
456    final long opID = nextOperationID.getAndIncrement();
457
458    final StringBuilder b = getRequestHeader("EXTENDED", opID, messageID);
459
460    b.append(" requestOID=\"");
461    b.append(request.getOID());
462    b.append('"');
463
464    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
465    logHandler.flush();
466
467    final long startTimeNanos = System.nanoTime();
468    final LDAPMessage responseMessage = requestHandler.processExtendedRequest(
469         messageID, request, controls);
470    final long eTimeNanos = System.nanoTime() - startTimeNanos;
471    final ExtendedResponseProtocolOp protocolOp =
472         responseMessage.getExtendedResponseProtocolOp();
473
474    generateResponse(b, "EXTENDED", opID, messageID, protocolOp.getResultCode(),
475         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
476         protocolOp.getReferralURLs(), eTimeNanos);
477
478    final String responseOID = protocolOp.getResponseOID();
479    if (responseOID != null)
480    {
481      b.append(" responseOID=\"");
482      b.append(responseOID);
483      b.append('"');
484    }
485
486    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
487    logHandler.flush();
488
489    return responseMessage;
490  }
491
492
493
494  /**
495   * {@inheritDoc}
496   */
497  @Override()
498  @NotNull()
499  public LDAPMessage processModifyRequest(final int messageID,
500                          @NotNull final ModifyRequestProtocolOp request,
501                          @NotNull final List<Control> controls)
502  {
503    final long opID = nextOperationID.getAndIncrement();
504
505    final StringBuilder b = getRequestHeader("MODIFY", opID, messageID);
506
507    b.append(" dn=\"");
508    b.append(request.getDN());
509    b.append('"');
510
511    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
512    logHandler.flush();
513
514    final long startTimeNanos = System.nanoTime();
515    final LDAPMessage responseMessage = requestHandler.processModifyRequest(
516         messageID, request, controls);
517    final long eTimeNanos = System.nanoTime() - startTimeNanos;
518    final ModifyResponseProtocolOp protocolOp =
519         responseMessage.getModifyResponseProtocolOp();
520
521    generateResponse(b, "MODIFY", opID, messageID, protocolOp.getResultCode(),
522         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
523         protocolOp.getReferralURLs(), eTimeNanos);
524
525    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
526    logHandler.flush();
527
528    return responseMessage;
529  }
530
531
532
533  /**
534   * {@inheritDoc}
535   */
536  @Override()
537  @NotNull()
538  public LDAPMessage processModifyDNRequest(final int messageID,
539                          @NotNull final ModifyDNRequestProtocolOp request,
540                          @NotNull final List<Control> controls)
541  {
542    final long opID = nextOperationID.getAndIncrement();
543
544    final StringBuilder b = getRequestHeader("MODDN", opID, messageID);
545
546    b.append(" dn=\"");
547    b.append(request.getDN());
548    b.append("\" newRDN=\"");
549    b.append(request.getNewRDN());
550    b.append("\" deleteOldRDN=");
551    b.append(request.deleteOldRDN());
552
553    final String newSuperior = request.getNewSuperiorDN();
554    if (newSuperior != null)
555    {
556      b.append(" newSuperior=\"");
557      b.append(newSuperior);
558      b.append('"');
559    }
560
561    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
562    logHandler.flush();
563
564    final long startTimeNanos = System.nanoTime();
565    final LDAPMessage responseMessage = requestHandler.processModifyDNRequest(
566         messageID, request, controls);
567    final long eTimeNanos = System.nanoTime() - startTimeNanos;
568    final ModifyDNResponseProtocolOp protocolOp =
569         responseMessage.getModifyDNResponseProtocolOp();
570
571    generateResponse(b, "MODDN", opID, messageID, protocolOp.getResultCode(),
572         protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
573         protocolOp.getReferralURLs(), eTimeNanos);
574
575    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
576    logHandler.flush();
577
578    return responseMessage;
579  }
580
581
582
583  /**
584   * {@inheritDoc}
585   */
586  @Override()
587  @NotNull()
588  public LDAPMessage processSearchRequest(final int messageID,
589                          @NotNull final SearchRequestProtocolOp request,
590                          @NotNull final List<Control> controls)
591  {
592    final long opID = nextOperationID.getAndIncrement();
593
594    final StringBuilder b = getRequestHeader("SEARCH", opID, messageID);
595
596    b.append(" base=\"");
597    b.append(request.getBaseDN());
598    b.append("\" scope=");
599    b.append(request.getScope().intValue());
600    b.append(" filter=\"");
601    request.getFilter().toString(b);
602    b.append("\" attrs=\"");
603
604    final List<String> attrList = request.getAttributes();
605    if (attrList.isEmpty())
606    {
607      b.append("ALL");
608    }
609    else
610    {
611      final Iterator<String> iterator = attrList.iterator();
612      while (iterator.hasNext())
613      {
614        b.append(iterator.next());
615        if (iterator.hasNext())
616        {
617          b.append(',');
618        }
619      }
620    }
621
622    b.append('"');
623
624    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
625    logHandler.flush();
626
627    final AtomicLong l = new AtomicLong(0L);
628    entryCounts.put(messageID, l);
629
630    try
631    {
632      final long startTimeNanos = System.nanoTime();
633      final LDAPMessage responseMessage = requestHandler.processSearchRequest(
634           messageID, request, controls);
635      final long eTimeNanos = System.nanoTime() - startTimeNanos;
636      final SearchResultDoneProtocolOp protocolOp =
637           responseMessage.getSearchResultDoneProtocolOp();
638
639      generateResponse(b, "SEARCH", opID, messageID, protocolOp.getResultCode(),
640           protocolOp.getDiagnosticMessage(), protocolOp.getMatchedDN(),
641           protocolOp.getReferralURLs(), eTimeNanos);
642
643      b.append(" entriesReturned=");
644      b.append(l.get());
645
646      logHandler.publish(new LogRecord(Level.INFO, b.toString()));
647      logHandler.flush();
648
649      return responseMessage;
650    }
651    finally
652    {
653      entryCounts.remove(messageID);
654    }
655  }
656
657
658
659  /**
660   * {@inheritDoc}
661   */
662  @Override()
663  public void processUnbindRequest(final int messageID,
664                   @NotNull final UnbindRequestProtocolOp request,
665                   @NotNull final List<Control> controls)
666  {
667    final StringBuilder b = getRequestHeader("UNBIND",
668         nextOperationID.getAndIncrement(), messageID);
669
670    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
671    logHandler.flush();
672
673    requestHandler.processUnbindRequest(messageID, request, controls);
674  }
675
676
677
678  /**
679   * Retrieves a string builder that can be used to construct a log message.
680   *
681   * @return  A string builder that can be used to construct a log message.
682   */
683  @NotNull()
684  private StringBuilder getBuffer()
685  {
686    StringBuilder b = buffers.get();
687    if (b == null)
688    {
689      b = new StringBuilder();
690      buffers.set(b);
691    }
692    else
693    {
694      b.setLength(0);
695    }
696
697    return b;
698  }
699
700
701
702  /**
703   * Adds a timestamp to the beginning of the provided buffer.
704   *
705   * @param  buffer  The buffer to which the timestamp should be added.
706   */
707  private void addTimestamp(@NotNull final StringBuilder buffer)
708  {
709    SimpleDateFormat dateFormat = timestampFormatters.get();
710    if (dateFormat == null)
711    {
712      dateFormat = new SimpleDateFormat("'['dd/MMM/yyyy:HH:mm:ss Z']'");
713      timestampFormatters.set(dateFormat);
714    }
715
716    buffer.append(dateFormat.format(new Date()));
717  }
718
719
720
721  /**
722   * Retrieves a {@code StringBuilder} with header information for a request log
723   * message for the specified type of operation.
724   *
725   * @param  messageType  The type of operation being requested.
726   *
727   * @return  A {@code StringBuilder} with header information appended for the
728   *          request;
729   */
730  @NotNull()
731  private StringBuilder getConnectionHeader(@NotNull final String messageType)
732  {
733    final StringBuilder b = getBuffer();
734    addTimestamp(b);
735    b.append(' ');
736    b.append(messageType);
737    b.append(" conn=");
738    b.append(clientConnection.getConnectionID());
739
740    return b;
741  }
742
743
744
745  /**
746   * Retrieves a {@code StringBuilder} with header information for a request log
747   * message for the specified type of operation.
748   *
749   * @param  opType  The type of operation being requested.
750   * @param  opID    The operation ID for the request.
751   * @param  msgID   The message ID for the request.
752   *
753   * @return  A {@code StringBuilder} with header information appended for the
754   *          request;
755   */
756  @NotNull()
757  private StringBuilder getRequestHeader(@NotNull final String opType,
758                             final long opID, final int msgID)
759  {
760    final StringBuilder b = getBuffer();
761    addTimestamp(b);
762    b.append(' ');
763    b.append(opType);
764    b.append(" REQUEST conn=");
765    b.append(clientConnection.getConnectionID());
766    b.append(" op=");
767    b.append(opID);
768    b.append(" msgID=");
769    b.append(msgID);
770
771    return b;
772  }
773
774
775
776  /**
777   * Writes information about the result of processing an operation to the
778   * given buffer.
779   *
780   * @param  b                  The buffer to which the information should be
781   *                            written.  The buffer will be cleared before
782   *                            adding any additional content.
783   * @param  opType             The type of operation that was processed.
784   * @param  opID               The operation ID for the response.
785   * @param  msgID              The message ID for the response.
786   * @param  resultCode         The result code for the response, if any.
787   * @param  diagnosticMessage  The diagnostic message for the response, if any.
788   * @param  matchedDN          The matched DN for the response, if any.
789   * @param  referralURLs       The referral URLs for the response, if any.
790   * @param  eTimeNanos         The length of time in nanoseconds required to
791   *                            process the operation.
792   */
793  private void generateResponse(@NotNull final StringBuilder b,
794                                @NotNull final String opType,
795                                final long opID, final int msgID,
796                                final int resultCode,
797                                @Nullable final String diagnosticMessage,
798                                @Nullable final String matchedDN,
799                                @NotNull final List<String> referralURLs,
800                                final long eTimeNanos)
801  {
802    b.setLength(0);
803    addTimestamp(b);
804    b.append(' ');
805    b.append(opType);
806    b.append(" RESULT conn=");
807    b.append(clientConnection.getConnectionID());
808    b.append(" op=");
809    b.append(opID);
810    b.append(" msgID=");
811    b.append(msgID);
812    b.append(" resultCode=");
813    b.append(resultCode);
814
815    if (diagnosticMessage != null)
816    {
817      b.append(" diagnosticMessage=\"");
818      b.append(diagnosticMessage);
819      b.append('"');
820    }
821
822    if (matchedDN != null)
823    {
824      b.append(" matchedDN=\"");
825      b.append(matchedDN);
826      b.append('"');
827    }
828
829    if (! referralURLs.isEmpty())
830    {
831      b.append(" referralURLs=\"");
832      final Iterator<String> iterator = referralURLs.iterator();
833      while (iterator.hasNext())
834      {
835        b.append(iterator.next());
836
837        if (iterator.hasNext())
838        {
839          b.append(',');
840        }
841      }
842
843      b.append('"');
844    }
845
846    DecimalFormat f = decimalFormatters.get();
847    if (f == null)
848    {
849      f = new DecimalFormat("0.000");
850      decimalFormatters.set(f);
851    }
852
853    b.append(" etime=");
854    b.append(f.format(eTimeNanos / 1_000_000.0d));
855  }
856
857
858
859  /**
860   * {@inheritDoc}
861   */
862  @Override()
863  @NotNull()
864  public ObjectPair<SearchResultEntryProtocolOp,Control[]> transformEntry(
865              final int messageID,
866              @NotNull final SearchResultEntryProtocolOp entry,
867              @NotNull final Control[] controls)
868  {
869    final AtomicLong l = entryCounts.get(messageID);
870    if (l != null)
871    {
872      l.incrementAndGet();
873    }
874
875    return new ObjectPair<>(entry, controls);
876  }
877}