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