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.util.Arrays;
027import java.util.List;
028import java.util.logging.Handler;
029import java.util.logging.Level;
030import java.util.logging.LogRecord;
031
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
034import com.unboundid.ldap.protocol.AddRequestProtocolOp;
035import com.unboundid.ldap.protocol.AddResponseProtocolOp;
036import com.unboundid.ldap.protocol.BindRequestProtocolOp;
037import com.unboundid.ldap.protocol.BindResponseProtocolOp;
038import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
039import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
040import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
041import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
042import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
043import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
044import com.unboundid.ldap.protocol.IntermediateResponseProtocolOp;
045import com.unboundid.ldap.protocol.LDAPMessage;
046import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
047import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
048import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
049import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
050import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
051import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
052import com.unboundid.ldap.protocol.SearchResultEntryProtocolOp;
053import com.unboundid.ldap.protocol.SearchResultReferenceProtocolOp;
054import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
055import com.unboundid.ldap.sdk.Control;
056import com.unboundid.ldap.sdk.Entry;
057import com.unboundid.ldap.sdk.LDAPException;
058import com.unboundid.ldap.sdk.ResultCode;
059import com.unboundid.ldif.LDIFModifyChangeRecord;
060import com.unboundid.util.NotMutable;
061import com.unboundid.util.ObjectPair;
062import com.unboundid.util.ThreadSafety;
063import com.unboundid.util.ThreadSafetyLevel;
064import com.unboundid.util.Validator;
065
066import static com.unboundid.util.StaticUtils.*;
067
068
069
070/**
071 * This class provides a request handler that may be used to write detailed
072 * information about the contents of all requests and responses that pass
073 * through it.  It will be also be associated with another request handler that
074 * will actually be used to handle the request.
075 */
076@NotMutable()
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class LDAPDebuggerRequestHandler
079       extends LDAPListenerRequestHandler
080       implements IntermediateResponseTransformer, SearchEntryTransformer,
081                  SearchReferenceTransformer
082{
083  /**
084   * The thread-local buffers that will be used to hold the log messages as they
085   * are being generated.
086   */
087  private static final ThreadLocal<StringBuilder> BUFFERS =
088       new ThreadLocal<StringBuilder>();
089
090
091
092  // The log handler that will be used to log the messages.
093  private final Handler logHandler;
094
095  // The request handler that actually will be used to process any requests
096  // received.
097  private final LDAPListenerRequestHandler requestHandler;
098
099  // The header string that will be used before each message.
100  private final String headerString;
101
102
103
104  /**
105   * Creates a new LDAP debugger request handler that will write detailed
106   * information about the contents of all requests and responses that pass
107   * through it using the provided log handler, and will process client requests
108   * using the provided request handler.
109   *
110   * @param  logHandler      The log handler that will be used to write detailed
111   *                         information about requests and responses.  Note
112   *                         that all messages will be logged at the INFO level.
113   *                         It must not be {@code null}.  Note that the log
114   *                         handler will not be automatically closed when the
115   *                         associated listener is shut down.
116   * @param  requestHandler  The request handler that will actually be used to
117   *                         process any requests received.  It must not be
118   *                         {@code null}.
119   */
120  public LDAPDebuggerRequestHandler(final Handler logHandler,
121              final LDAPListenerRequestHandler requestHandler)
122  {
123    Validator.ensureNotNull(logHandler, requestHandler);
124
125    this.logHandler     = logHandler;
126    this.requestHandler = requestHandler;
127
128    headerString = null;
129  }
130
131
132
133  /**
134   * Creates a new LDAP debugger request handler that will write detailed
135   * information about the contents of all requests and responses that pass
136   * through it using the provided log handler, and will process client requests
137   * using the provided request handler.
138   *
139   * @param  logHandler      The log handler that will be used to write detailed
140   *                         information about requests and responses.  Note
141   *                         that all messages will be logged at the INFO level.
142   *                         It must not be {@code null}.
143   * @param  requestHandler  The request handler that will actually be used to
144   *                         process any requests received.  It must not be
145   *                         {@code null}.
146   * @param  headerString    The string that should be given as the first line
147   *                         of every log message.
148   */
149  private LDAPDebuggerRequestHandler(final Handler logHandler,
150               final LDAPListenerRequestHandler requestHandler,
151               final String headerString)
152  {
153    Validator.ensureNotNull(logHandler, requestHandler);
154
155    this.logHandler     = logHandler;
156    this.requestHandler = requestHandler;
157    this.headerString    = headerString;
158  }
159
160
161
162  /**
163   * {@inheritDoc}
164   */
165  @Override()
166  public LDAPDebuggerRequestHandler newInstance(
167              final LDAPListenerClientConnection connection)
168         throws LDAPException
169  {
170    final StringBuilder b = getBuffer();
171    final Socket s = connection.getSocket();
172    b.append("conn=");
173    b.append(connection.getConnectionID());
174    b.append(" from=\"");
175    b.append(s.getInetAddress().getHostAddress());
176    b.append(':');
177    b.append(s.getPort());
178    b.append("\" to=\"");
179    b.append(s.getLocalAddress().getHostAddress());
180    b.append(':');
181    b.append(s.getLocalPort());
182    b.append('"');
183    b.append(EOL);
184
185    final String header = b.toString();
186
187    final LDAPDebuggerRequestHandler h = new LDAPDebuggerRequestHandler(
188         logHandler, requestHandler.newInstance(connection), header);
189
190    connection.addIntermediateResponseTransformer(h);
191    connection.addSearchEntryTransformer(h);
192    connection.addSearchReferenceTransformer(h);
193
194    logHandler.publish(new LogRecord(Level.INFO, "CONNECT " + header));
195
196    return h;
197  }
198
199
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override()
205  public void closeInstance()
206  {
207    final StringBuilder b = getBuffer();
208    b.append("DISCONNECT ");
209    b.append(headerString);
210
211    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
212
213    requestHandler.closeInstance();
214  }
215
216
217
218  /**
219   * {@inheritDoc}
220   */
221  @Override()
222  public void processAbandonRequest(final int messageID,
223                                    final AbandonRequestProtocolOp request,
224                                    final List<Control> controls)
225  {
226    final StringBuilder b = getBuffer();
227    appendHeader(b, messageID);
228
229    b.append("     Abandon Request Protocol Op:").append(EOL);
230    b.append("          ID to Abandon:  ").append(request.getIDToAbandon()).
231         append(EOL);
232
233    appendControls(b, controls);
234    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
235
236    requestHandler.processAbandonRequest(messageID, request, controls);
237  }
238
239
240
241  /**
242   * {@inheritDoc}
243   */
244  @Override()
245  public LDAPMessage processAddRequest(final int messageID,
246                                       final AddRequestProtocolOp request,
247                                       final List<Control> controls)
248  {
249    final StringBuilder b = getBuffer();
250    appendHeader(b, messageID);
251
252    b.append("     Add Request Protocol Op:").append(EOL);
253
254    final Entry e = new Entry(request.getDN(), request.getAttributes());
255    final String[] ldifLines = e.toLDIF(80);
256    for (final String line : ldifLines)
257    {
258      b.append("          ").append(line).append(EOL);
259    }
260
261    appendControls(b, controls);
262    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
263
264    final LDAPMessage responseMessage = requestHandler.processAddRequest(
265         messageID, request, controls);
266
267    b.setLength(0);
268    appendHeader(b, responseMessage.getMessageID());
269    b.append("     Add Response Protocol Op:").append(EOL);
270
271    final AddResponseProtocolOp protocolOp =
272         responseMessage.getAddResponseProtocolOp();
273    appendResponse(b, protocolOp.getResultCode(),
274         protocolOp.getDiagnosticMessage(),
275         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
276
277    appendControls(b, responseMessage.getControls());
278    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
279
280    return responseMessage;
281  }
282
283
284
285  /**
286   * {@inheritDoc}
287   */
288  @Override()
289  public LDAPMessage processBindRequest(final int messageID,
290                                        final BindRequestProtocolOp request,
291                                        final List<Control> controls)
292  {
293    final StringBuilder b = getBuffer();
294    appendHeader(b, messageID);
295
296    b.append("     Bind Request Protocol Op:").append(EOL);
297    b.append("          LDAP Version:  ").append(request.getVersion()).
298         append(EOL);
299    b.append("          Bind DN:  ").append(request.getBindDN()).append(EOL);
300
301    switch (request.getCredentialsType())
302    {
303      case BindRequestProtocolOp.CRED_TYPE_SIMPLE:
304        b.append("          Credentials Type:  SIMPLE").append(EOL);
305        b.append("               Password:  ").
306             append(request.getSimplePassword()).append(EOL);
307        break;
308
309      case BindRequestProtocolOp.CRED_TYPE_SASL:
310        b.append("          Credentials Type:  SASL").append(EOL);
311        b.append("               Mechanism:  ").
312             append(request.getSASLMechanism()).append(EOL);
313
314        final ASN1OctetString saslCredentials = request.getSASLCredentials();
315        if (saslCredentials != null)
316        {
317          b.append("               Encoded Credentials:");
318          b.append(EOL);
319          toHexPlusASCII(saslCredentials.getValue(), 20, b);
320        }
321        break;
322    }
323
324    appendControls(b, controls);
325    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
326
327    final LDAPMessage responseMessage = requestHandler.processBindRequest(
328         messageID, request, controls);
329
330    b.setLength(0);
331    appendHeader(b, responseMessage.getMessageID());
332    b.append("     Bind Response Protocol Op:").append(EOL);
333
334    final BindResponseProtocolOp protocolOp =
335         responseMessage.getBindResponseProtocolOp();
336    appendResponse(b, protocolOp.getResultCode(),
337         protocolOp.getDiagnosticMessage(),
338         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
339
340    final ASN1OctetString serverSASLCredentials =
341         protocolOp.getServerSASLCredentials();
342    if (serverSASLCredentials != null)
343    {
344      b.append("               Encoded Server SASL Credentials:");
345      b.append(EOL);
346      toHexPlusASCII(serverSASLCredentials.getValue(), 20, b);
347    }
348
349    appendControls(b, responseMessage.getControls());
350    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
351
352    return responseMessage;
353  }
354
355
356
357  /**
358   * {@inheritDoc}
359   */
360  @Override()
361  public LDAPMessage processCompareRequest(final int messageID,
362                          final CompareRequestProtocolOp request,
363                          final List<Control> controls)
364  {
365    final StringBuilder b = getBuffer();
366    appendHeader(b, messageID);
367
368    b.append("     Compare Request Protocol Op:").append(EOL);
369    b.append("          DN:  ").append(request.getDN()).append(EOL);
370    b.append("          Attribute Type:  ").append(request.getAttributeName()).
371         append(EOL);
372    b.append("          Assertion Value:  ").
373         append(request.getAssertionValue().stringValue()).append(EOL);
374
375    appendControls(b, controls);
376    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
377
378    final LDAPMessage responseMessage = requestHandler.processCompareRequest(
379         messageID, request, controls);
380
381    b.setLength(0);
382    appendHeader(b, responseMessage.getMessageID());
383    b.append("     Compare Response Protocol Op:").append(EOL);
384
385    final CompareResponseProtocolOp protocolOp =
386         responseMessage.getCompareResponseProtocolOp();
387    appendResponse(b, protocolOp.getResultCode(),
388         protocolOp.getDiagnosticMessage(),
389         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
390
391    appendControls(b, responseMessage.getControls());
392    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
393
394    return responseMessage;
395  }
396
397
398
399  /**
400   * {@inheritDoc}
401   */
402  @Override()
403  public LDAPMessage processDeleteRequest(final int messageID,
404                                          final DeleteRequestProtocolOp request,
405                                          final List<Control> controls)
406  {
407    final StringBuilder b = getBuffer();
408    appendHeader(b, messageID);
409
410    b.append("     Delete Request Protocol Op:").append(EOL);
411    b.append("          DN:  ").append(request.getDN()).append(EOL);
412
413    appendControls(b, controls);
414    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
415
416    final LDAPMessage responseMessage = requestHandler.processDeleteRequest(
417         messageID, request, controls);
418
419    b.setLength(0);
420    appendHeader(b, responseMessage.getMessageID());
421    b.append("     Delete Response Protocol Op:").append(EOL);
422
423    final DeleteResponseProtocolOp protocolOp =
424         responseMessage.getDeleteResponseProtocolOp();
425    appendResponse(b, protocolOp.getResultCode(),
426         protocolOp.getDiagnosticMessage(),
427         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
428
429    appendControls(b, responseMessage.getControls());
430    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
431
432    return responseMessage;
433  }
434
435
436
437  /**
438   * {@inheritDoc}
439   */
440  @Override()
441  public LDAPMessage processExtendedRequest(final int messageID,
442                          final ExtendedRequestProtocolOp request,
443                          final List<Control> controls)
444  {
445    final StringBuilder b = getBuffer();
446    appendHeader(b, messageID);
447
448    b.append("     Extended Request Protocol Op:").append(EOL);
449    b.append("          Request OID:  ").append(request.getOID()).append(EOL);
450
451    final ASN1OctetString requestValue = request.getValue();
452    if (requestValue != null)
453    {
454      b.append("          Encoded Request Value:");
455      b.append(EOL);
456      toHexPlusASCII(requestValue.getValue(), 15, b);
457    }
458
459    appendControls(b, controls);
460    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
461
462    final LDAPMessage responseMessage = requestHandler.processExtendedRequest(
463         messageID, request, controls);
464
465    b.setLength(0);
466    appendHeader(b, responseMessage.getMessageID());
467    b.append("     Extended Response Protocol Op:").append(EOL);
468
469    final ExtendedResponseProtocolOp protocolOp =
470         responseMessage.getExtendedResponseProtocolOp();
471    appendResponse(b, protocolOp.getResultCode(),
472         protocolOp.getDiagnosticMessage(),
473         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
474
475    final String responseOID = protocolOp.getResponseOID();
476    if (responseOID != null)
477    {
478      b.append("          Response OID:  ").append(responseOID).append(EOL);
479    }
480
481    final ASN1OctetString responseValue = protocolOp.getResponseValue();
482    if (responseValue != null)
483    {
484      b.append("          Encoded Response Value:");
485      b.append(EOL);
486      toHexPlusASCII(responseValue.getValue(), 15, b);
487    }
488
489    appendControls(b, responseMessage.getControls());
490    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
491
492    return responseMessage;
493  }
494
495
496
497  /**
498   * {@inheritDoc}
499   */
500  @Override()
501  public LDAPMessage processModifyRequest(final int messageID,
502                                          final ModifyRequestProtocolOp request,
503                                          final List<Control> controls)
504  {
505    final StringBuilder b = getBuffer();
506    appendHeader(b, messageID);
507
508    b.append("     Modify Request Protocol Op:").append(EOL);
509
510    final LDIFModifyChangeRecord changeRecord =
511         new LDIFModifyChangeRecord(request.getDN(),
512              request.getModifications());
513    final String[] ldifLines = changeRecord.toLDIF(80);
514    for (final String line : ldifLines)
515    {
516      b.append("          ").append(line).append(EOL);
517    }
518
519    appendControls(b, controls);
520    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
521
522    final LDAPMessage responseMessage = requestHandler.processModifyRequest(
523         messageID, request, controls);
524
525    b.setLength(0);
526    appendHeader(b, responseMessage.getMessageID());
527    b.append("     Modify Response Protocol Op:").append(EOL);
528
529    final ModifyResponseProtocolOp protocolOp =
530         responseMessage.getModifyResponseProtocolOp();
531    appendResponse(b, protocolOp.getResultCode(),
532         protocolOp.getDiagnosticMessage(),
533         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
534
535    appendControls(b, responseMessage.getControls());
536    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
537
538    return responseMessage;
539  }
540
541
542
543  /**
544   * {@inheritDoc}
545   */
546  @Override()
547  public LDAPMessage processModifyDNRequest(final int messageID,
548                          final ModifyDNRequestProtocolOp request,
549                          final List<Control> controls)
550  {
551    final StringBuilder b = getBuffer();
552    appendHeader(b, messageID);
553
554    b.append("     Modify DN Request Protocol Op:").append(EOL);
555    b.append("          DN:  ").append(request.getDN()).append(EOL);
556    b.append("          New RDN:  ").append(request.getNewRDN()).append(EOL);
557    b.append("          Delete Old RDN:  ").append(request.deleteOldRDN()).
558         append(EOL);
559
560    final String newSuperior = request.getNewSuperiorDN();
561    if (newSuperior != null)
562    {
563      b.append("          New Superior DN:  ").append(newSuperior).append(EOL);
564    }
565
566    appendControls(b, controls);
567    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
568
569    final LDAPMessage responseMessage = requestHandler.processModifyDNRequest(
570         messageID, request, controls);
571
572    b.setLength(0);
573    appendHeader(b, responseMessage.getMessageID());
574    b.append("     Modify DN Response Protocol Op:").append(EOL);
575
576    final ModifyDNResponseProtocolOp protocolOp =
577         responseMessage.getModifyDNResponseProtocolOp();
578    appendResponse(b, protocolOp.getResultCode(),
579         protocolOp.getDiagnosticMessage(),
580         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
581
582    appendControls(b, responseMessage.getControls());
583    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
584
585    return responseMessage;
586  }
587
588
589
590  /**
591   * {@inheritDoc}
592   */
593  @Override()
594  public LDAPMessage processSearchRequest(final int messageID,
595                                          final SearchRequestProtocolOp request,
596                                          final List<Control> controls)
597  {
598    final StringBuilder b = getBuffer();
599    appendHeader(b, messageID);
600
601    b.append("     Search Request Protocol Op:").append(EOL);
602    b.append("          Base DN:  ").append(request.getBaseDN()).append(EOL);
603    b.append("          Scope:  ").append(request.getScope()).append(EOL);
604    b.append("          Dereference Policy:  ").
605         append(request.getDerefPolicy()).append(EOL);
606    b.append("          Size Limit:  ").append(request.getSizeLimit()).
607         append(EOL);
608    b.append("          Time Limit:  ").append(request.getSizeLimit()).
609         append(EOL);
610    b.append("          Types Only:  ").append(request.typesOnly()).append(EOL);
611    b.append("          Filter:  ");
612    request.getFilter().toString(b);
613    b.append(EOL);
614
615    final List<String> attributes = request.getAttributes();
616    if (! attributes.isEmpty())
617    {
618      b.append("          Requested Attributes:").append(EOL);
619      for (final String attr : attributes)
620      {
621        b.append("               ").append(attr).append(EOL);
622      }
623    }
624
625    appendControls(b, controls);
626    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
627
628    final LDAPMessage responseMessage = requestHandler.processSearchRequest(
629         messageID, request, controls);
630
631    b.setLength(0);
632    appendHeader(b, responseMessage.getMessageID());
633    b.append("     Search Result Done Protocol Op:").append(EOL);
634
635    final SearchResultDoneProtocolOp protocolOp =
636         responseMessage.getSearchResultDoneProtocolOp();
637    appendResponse(b, protocolOp.getResultCode(),
638         protocolOp.getDiagnosticMessage(),
639         protocolOp.getMatchedDN(), protocolOp.getReferralURLs());
640
641    appendControls(b, responseMessage.getControls());
642    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
643
644    return responseMessage;
645  }
646
647
648
649  /**
650   * {@inheritDoc}
651   */
652  @Override()
653  public void processUnbindRequest(final int messageID,
654                                   final UnbindRequestProtocolOp request,
655                                   final List<Control> controls)
656  {
657    final StringBuilder b = getBuffer();
658    appendHeader(b, messageID);
659
660    b.append("     Unbind Request Protocol Op:").append(EOL);
661
662    appendControls(b, controls);
663    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
664
665    requestHandler.processUnbindRequest(messageID, request, controls);
666  }
667
668
669
670  /**
671   * Retrieves a {@code StringBuilder} that may be used to generate a log
672   * message.
673   *
674   * @return  A {@code StringBuilder} containing the LDAP message header.
675   */
676  private static StringBuilder getBuffer()
677  {
678    StringBuilder b = BUFFERS.get();
679    if (b == null)
680    {
681      b = new StringBuilder();
682      BUFFERS.set(b);
683    }
684    else
685    {
686      b.setLength(0);
687    }
688
689    return b;
690  }
691
692
693
694  /**
695   * Appends an LDAP message header to the provided buffer.
696   *
697   * @param  b          The buffer to which to write the header.
698   * @param  messageID  The message ID for the LDAP message.
699   */
700  private void appendHeader(final StringBuilder b, final int messageID)
701  {
702    b.append(headerString);
703    b.append("LDAP Message:").append(EOL);
704    b.append("     Message ID:  ").append(messageID).append(EOL);
705  }
706
707
708
709  /**
710   * Appends information about an LDAP response to the given buffer.
711   *
712   * @param  b                  The buffer to which to append the information.
713   * @param  resultCode         The result code for the response.
714   * @param  diagnosticMessage  The diagnostic message for the response, if any.
715   * @param  matchedDN          The matched DN for the response, if any.
716   * @param  referralURLs       The referral URLs for the response, if any.
717   */
718  private static void appendResponse(final StringBuilder b,
719                                     final int resultCode,
720                                     final String diagnosticMessage,
721                                     final String matchedDN,
722                                     final List<String> referralURLs)
723  {
724    b.append("          Result Code:  ").append(ResultCode.valueOf(resultCode)).
725         append(EOL);
726
727    if (diagnosticMessage != null)
728    {
729      b.append("          Diagnostic Message:  ").append(diagnosticMessage).
730           append(EOL);
731    }
732
733    if (matchedDN != null)
734    {
735      b.append("          Matched DN:  ").append(matchedDN).append(EOL);
736    }
737
738    if (! referralURLs.isEmpty())
739    {
740      b.append("          Referral URLs:").append(EOL);
741      for (final String url : referralURLs)
742      {
743        b.append("               ").append(url).append(EOL);
744      }
745    }
746  }
747
748
749
750  /**
751   * Appends information about the provided set of controls to the given buffer.
752   * A trailing EOL will also be appended.
753   *
754   * @param  b         The buffer to which to append the control information.
755   * @param  controls  The set of controls to be appended to the buffer.
756   */
757  private static void appendControls(final StringBuilder b,
758                                     final List<Control> controls)
759  {
760    if (! controls.isEmpty())
761    {
762      b.append("     Controls:").append(EOL);
763
764      int index = 1;
765      for (final Control c : controls)
766      {
767        b.append("          Control ");
768        b.append(index++);
769        b.append(EOL);
770        b.append("               OID:  ");
771        b.append(c.getOID());
772        b.append(EOL);
773        b.append("               Is Critical:  ");
774        b.append(c.isCritical());
775        b.append(EOL);
776
777        final ASN1OctetString value = c.getValue();
778        if ((value != null) && (value.getValueLength() > 0))
779        {
780          b.append("               Encoded Value:");
781          b.append(EOL);
782          toHexPlusASCII(value.getValue(), 20, b);
783        }
784
785        // If it is a subclass of Control rather than just a generic one, then
786        // it might have a useful toString representation, so provide it.
787        if (! c.getClass().getName().equals(Control.class.getName()))
788        {
789          b.append("               String Representation:  ");
790          c.toString(b);
791          b.append(EOL);
792        }
793      }
794    }
795  }
796
797
798
799  /**
800   * Appends information about the provided set of controls to the given buffer.
801   *
802   * @param  b         The buffer to which to append the control information.
803   * @param  controls  The set of controls to be appended to the buffer.
804   */
805  private static void appendControls(final StringBuilder b,
806                                     final Control[] controls)
807  {
808    appendControls(b, Arrays.asList(controls));
809  }
810
811
812
813  /**
814   * {@inheritDoc}
815   */
816  public ObjectPair<IntermediateResponseProtocolOp,Control[]>
817              transformIntermediateResponse(final int messageID,
818                   final IntermediateResponseProtocolOp response,
819                   final Control[] controls)
820  {
821    final StringBuilder b = getBuffer();
822    appendHeader(b, messageID);
823
824    b.append("     Intermediate Response Protocol Op:").append(EOL);
825
826    final String oid = response.getOID();
827    if (oid != null)
828    {
829      b.append("          OID:  ").append(oid).append(EOL);
830    }
831
832    final ASN1OctetString value = response.getValue();
833    if (value != null)
834    {
835      b.append("          Encoded Value:");
836      b.append(EOL);
837      toHexPlusASCII(value.getValue(), 15, b);
838    }
839
840    appendControls(b, controls);
841    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
842
843    return new ObjectPair<IntermediateResponseProtocolOp,Control[]>(response,
844         controls);
845  }
846
847
848
849  /**
850   * {@inheritDoc}
851   */
852  public ObjectPair<SearchResultEntryProtocolOp,Control[]> transformEntry(
853              final int messageID, final SearchResultEntryProtocolOp entry,
854              final Control[] controls)
855  {
856    final StringBuilder b = getBuffer();
857    appendHeader(b, messageID);
858
859    b.append("     Search Result Entry Protocol Op:").append(EOL);
860
861    final Entry e = new Entry(entry.getDN(), entry.getAttributes());
862    final String[] ldifLines = e.toLDIF(80);
863    for (final String line : ldifLines)
864    {
865      b.append("          ").append(line).append(EOL);
866    }
867
868    appendControls(b, controls);
869    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
870
871    return new ObjectPair<SearchResultEntryProtocolOp,Control[]>(entry,
872         controls);
873  }
874
875
876
877  /**
878   * {@inheritDoc}
879   */
880  public ObjectPair<SearchResultReferenceProtocolOp,Control[]>
881              transformReference(final int messageID,
882                   final SearchResultReferenceProtocolOp reference,
883                   final Control[] controls)
884  {
885    final StringBuilder b = getBuffer();
886    appendHeader(b, messageID);
887
888    b.append("     Search Result Reference Protocol Op:").append(EOL);
889    b.append("          Referral URLs:").append(EOL);
890
891    for (final String url : reference.getReferralURLs())
892    {
893      b.append("               ").append(url).append(EOL);
894    }
895
896    appendControls(b, controls);
897    logHandler.publish(new LogRecord(Level.INFO, b.toString()));
898
899    return new ObjectPair<SearchResultReferenceProtocolOp,Control[]>(reference,
900         controls);
901  }
902}