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