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