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 */
021 package com.unboundid.ldap.listener;
022
023
024
025 import java.net.Socket;
026 import java.text.DecimalFormat;
027 import java.text.SimpleDateFormat;
028 import java.util.Date;
029 import java.util.Iterator;
030 import java.util.List;
031 import java.util.concurrent.ConcurrentHashMap;
032 import java.util.concurrent.atomic.AtomicLong;
033 import java.util.logging.Handler;
034 import java.util.logging.Level;
035 import java.util.logging.LogRecord;
036
037 import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
038 import com.unboundid.ldap.protocol.AddRequestProtocolOp;
039 import com.unboundid.ldap.protocol.AddResponseProtocolOp;
040 import com.unboundid.ldap.protocol.BindRequestProtocolOp;
041 import com.unboundid.ldap.protocol.BindResponseProtocolOp;
042 import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
043 import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
044 import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
045 import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
046 import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
047 import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
048 import com.unboundid.ldap.protocol.LDAPMessage;
049 import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
050 import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
051 import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
052 import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
053 import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
054 import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
055 import com.unboundid.ldap.protocol.SearchResultEntryProtocolOp;
056 import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
057 import com.unboundid.ldap.sdk.Control;
058 import com.unboundid.ldap.sdk.LDAPException;
059 import com.unboundid.util.NotMutable;
060 import com.unboundid.util.ObjectPair;
061 import com.unboundid.util.ThreadSafety;
062 import com.unboundid.util.ThreadSafetyLevel;
063 import 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)
075 public 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 }