001/* 002 * Copyright 2010-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-2017 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}