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