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}