001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2019 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.util;
022
023
024
025import java.io.Serializable;
026import java.util.EnumSet;
027import java.util.Properties;
028import java.util.Set;
029import java.util.StringTokenizer;
030import java.util.logging.Level;
031import java.util.logging.Logger;
032
033import com.unboundid.asn1.ASN1Buffer;
034import com.unboundid.asn1.ASN1Element;
035import com.unboundid.ldap.protocol.LDAPResponse;
036import com.unboundid.ldap.sdk.AbstractConnectionPool;
037import com.unboundid.ldap.sdk.DisconnectType;
038import com.unboundid.ldap.sdk.Entry;
039import com.unboundid.ldap.sdk.InternalSDKHelper;
040import com.unboundid.ldap.sdk.LDAPConnection;
041import com.unboundid.ldap.sdk.LDAPRequest;
042import com.unboundid.ldap.sdk.Version;
043import com.unboundid.ldif.LDIFRecord;
044
045
046
047/**
048 * This class provides a means of enabling and configuring debugging in the LDAP
049 * SDK.
050 * <BR><BR>
051 * Access to debug information can be enabled through applications that use the
052 * SDK by calling the {@link Debug#setEnabled} methods, or it can also be
053 * enabled without any code changes through the use of system properties.  In
054 * particular, the {@link Debug#PROPERTY_DEBUG_ENABLED},
055 * {@link Debug#PROPERTY_DEBUG_LEVEL}, and {@link Debug#PROPERTY_DEBUG_TYPE}
056 * properties may be used to control debugging without the need to alter any
057 * code within the application that uses the SDK.
058 * <BR><BR>
059 * The LDAP SDK debugging subsystem uses the Java logging framework available
060 * through the {@code java.util.logging} package with a logger name of
061 * "{@code com.unboundid.ldap.sdk}".  The {@link Debug#getLogger} method may
062 * be used to access the logger instance used by the LDAP SDK.
063 * <BR><BR>
064 * <H2>Example</H2>
065 * The following example demonstrates the process that may be used to enable
066 * debugging within the LDAP SDK and write information about all messages with
067 * a {@code WARNING} level or higher to a specified file:
068 * <PRE>
069 * Debug.setEnabled(true);
070 * Logger logger = Debug.getLogger();
071 *
072 * FileHandler fileHandler = new FileHandler(logFilePath);
073 * fileHandler.setLevel(Level.WARNING);
074 * logger.addHandler(fileHandler);
075 * </PRE>
076 */
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class Debug
079       implements Serializable
080{
081  /**
082   * The name of the system property that will be used to enable debugging in
083   * the UnboundID LDAP SDK for Java.  The fully-qualified name for this
084   * property is "{@code com.unboundid.ldap.sdk.debug.enabled}".  If it is set,
085   * then it should have a value of either "true" or "false".
086   */
087  public static final String PROPERTY_DEBUG_ENABLED =
088       "com.unboundid.ldap.sdk.debug.enabled";
089
090
091
092  /**
093   * The name of the system property that may be used to indicate whether stack
094   * trace information for the thread calling the debug method should be
095   * included in debug log messages.  The fully-qualified name for this property
096   * is "{@code com.unboundid.ldap.sdk.debug.includeStackTrace}".  If it is set,
097   * then it should have a value of either "true" or "false".
098   */
099  public static final String PROPERTY_INCLUDE_STACK_TRACE =
100       "com.unboundid.ldap.sdk.debug.includeStackTrace";
101
102
103
104  /**
105   * The name of the system property that will be used to set the initial level
106   * for the debug logger.  The fully-qualified name for this property is
107   * "{@code com.unboundid.ldap.sdk.debug.level}".  If it is set, then it should
108   * be one of the strings "{@code SEVERE}", "{@code WARNING}", "{@code INFO}",
109   * "{@code CONFIG}", "{@code FINE}", "{@code FINER}", or "{@code FINEST}".
110   */
111  public static final String PROPERTY_DEBUG_LEVEL =
112       "com.unboundid.ldap.sdk.debug.level";
113
114
115
116  /**
117   * The name of the system property that will be used to indicate that
118   * debugging should be enabled for specific types of messages.  The
119   * fully-qualified name for this property is
120   * "{@code com.unboundid.ldap.sdk.debug.type}". If it is set, then it should
121   * be a comma-delimited list of the names of the desired debug types.  See the
122   * {@link DebugType} enum for the available debug types.
123   */
124  public static final String PROPERTY_DEBUG_TYPE =
125       "com.unboundid.ldap.sdk.debug.type";
126
127
128
129  /**
130   * The name of the system property that will be used to indicate whether the
131   * LDAP SDK should default to including information about the exception's
132   * cause in an exception message obtained from the
133   * {@link StaticUtils#getExceptionMessage(Throwable)} method.  By default,
134   * the cause will not be included in most messages.
135   */
136  public static final String PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES =
137       "com.unboundid.ldap.sdk.debug.includeCauseInExceptionMessages";
138
139
140
141  /**
142   * The name of the system property that will be used to indicate whether the
143   * LDAP SDK should default to including a full stack trace (albeit in
144   * condensed form) in an exception message obtained from the
145   * {@link StaticUtils#getExceptionMessage(Throwable)} method.  By default,
146   * stack traces will not be included in most messages.
147   */
148  public static final String
149       PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES =
150            "com.unboundid.ldap.sdk.debug.includeStackTraceInExceptionMessages";
151
152
153
154  /**
155   * The name that will be used for the Java logger that will actually handle
156   * the debug messages if debugging is enabled.
157   */
158  public static final String LOGGER_NAME = "com.unboundid.ldap.sdk";
159
160
161
162  /**
163   * The logger that will be used to handle the debug messages if debugging is
164   * enabled.
165   */
166  private static final Logger logger = Logger.getLogger(LOGGER_NAME);
167
168
169
170  /**
171   * The serial version UID for this serializable class.
172   */
173  private static final long serialVersionUID = -6079754380415146030L;
174
175
176
177  // Indicates whether any debugging is currently enabled for the SDK.
178  private static boolean debugEnabled;
179
180  // Indicates whether to capture a thread stack trace whenever a debug message
181  // is logged.
182  private static boolean includeStackTrace;
183
184  // The set of debug types for which debugging is enabled.
185  private static EnumSet<DebugType> debugTypes;
186
187
188
189  static
190  {
191    initialize(StaticUtils.getSystemProperties(PROPERTY_DEBUG_ENABLED,
192         PROPERTY_DEBUG_LEVEL, PROPERTY_DEBUG_TYPE,
193         PROPERTY_INCLUDE_STACK_TRACE));
194  }
195
196
197
198  /**
199   * Prevent this class from being instantiated.
200   */
201  private Debug()
202  {
203    // No implementation is required.
204  }
205
206
207
208  /**
209   * Initializes this debugger with the default settings.  Debugging will be
210   * disabled, the set of debug types will include all types, and the debug
211   * level will be "ALL".
212   */
213  public static void initialize()
214  {
215    includeStackTrace = false;
216    debugEnabled      = false;
217    debugTypes        = EnumSet.allOf(DebugType.class);
218
219    StaticUtils.setLoggerLevel(logger, Level.ALL);
220  }
221
222
223
224  /**
225   * Initializes this debugger with settings from the provided set of
226   * properties.  Any debug setting that isn't configured in the provided
227   * properties will be initialized with its default value.
228   *
229   * @param  properties  The set of properties to use to initialize this
230   *                     debugger.
231   */
232  public static void initialize(final Properties properties)
233  {
234    // First, apply the default values for the properties.
235    initialize();
236    if ((properties == null) || properties.isEmpty())
237    {
238      // No properties were provided, so we don't need to do anything.
239      return;
240    }
241
242    final String enabledProp = properties.getProperty(PROPERTY_DEBUG_ENABLED);
243    if ((enabledProp != null) && (! enabledProp.isEmpty()))
244    {
245      if (enabledProp.equalsIgnoreCase("true"))
246      {
247        debugEnabled = true;
248      }
249      else if (enabledProp.equalsIgnoreCase("false"))
250      {
251        debugEnabled = false;
252      }
253      else
254      {
255        throw new IllegalArgumentException("Invalid value '" + enabledProp +
256                                           "' for property " +
257                                           PROPERTY_DEBUG_ENABLED +
258                                           ".  The value must be either " +
259                                           "'true' or 'false'.");
260      }
261    }
262
263    final String stackProp =
264         properties.getProperty(PROPERTY_INCLUDE_STACK_TRACE);
265    if ((stackProp != null) && (! stackProp.isEmpty()))
266    {
267      if (stackProp.equalsIgnoreCase("true"))
268      {
269        includeStackTrace = true;
270      }
271      else if (stackProp.equalsIgnoreCase("false"))
272      {
273        includeStackTrace = false;
274      }
275      else
276      {
277        throw new IllegalArgumentException("Invalid value '" + stackProp +
278                                           "' for property " +
279                                           PROPERTY_INCLUDE_STACK_TRACE +
280                                           ".  The value must be either " +
281                                           "'true' or 'false'.");
282      }
283    }
284
285    final String typesProp = properties.getProperty(PROPERTY_DEBUG_TYPE);
286    if ((typesProp != null) && (! typesProp.isEmpty()))
287    {
288      debugTypes = EnumSet.noneOf(DebugType.class);
289      final StringTokenizer t = new StringTokenizer(typesProp, ", ");
290      while (t.hasMoreTokens())
291      {
292        final String debugTypeName = t.nextToken();
293        final DebugType debugType = DebugType.forName(debugTypeName);
294        if (debugType == null)
295        {
296          // Throw a runtime exception to indicate that the debug type is
297          // invalid.
298          throw new IllegalArgumentException("Invalid value '" + debugTypeName +
299                      "' for property " + PROPERTY_DEBUG_TYPE +
300                      ".  Allowed values include:  " +
301                      DebugType.getTypeNameList() + '.');
302        }
303        else
304        {
305          debugTypes.add(debugType);
306        }
307      }
308    }
309
310    final String levelProp = properties.getProperty(PROPERTY_DEBUG_LEVEL);
311    if ((levelProp != null) && (! levelProp.isEmpty()))
312    {
313      StaticUtils.setLoggerLevel(logger, Level.parse(levelProp));
314    }
315  }
316
317
318
319  /**
320   * Retrieves the logger that will be used to write the debug messages.
321   *
322   * @return  The logger that will be used to write the debug messages.
323   */
324  public static Logger getLogger()
325  {
326    return logger;
327  }
328
329
330
331  /**
332   * Indicates whether any form of debugging is enabled.
333   *
334   * @return  {@code true} if debugging is enabled, or {@code false} if not.
335   */
336  public static boolean debugEnabled()
337  {
338    return debugEnabled;
339  }
340
341
342
343  /**
344   * Indicates whether debugging is enabled for messages of the specified debug
345   * type.
346   *
347   * @param  debugType  The debug type for which to make the determination.
348   *
349   * @return  {@code true} if debugging is enabled for messages of the specified
350   *          debug type, or {@code false} if not.
351   */
352  public static boolean debugEnabled(final DebugType debugType)
353  {
354    return (debugEnabled && debugTypes.contains(debugType));
355  }
356
357
358
359  /**
360   * Specifies whether debugging should be enabled.  If it should be, then it
361   * will be enabled for all debug types.
362   *
363   * @param  enabled  Specifies whether debugging should be enabled.
364   */
365  public static void setEnabled(final boolean enabled)
366  {
367    debugTypes   = EnumSet.allOf(DebugType.class);
368    debugEnabled = enabled;
369  }
370
371
372
373  /**
374   * Specifies whether debugging should be enabled.  If it should be, then it
375   * will be enabled for all debug types in the provided set.
376   *
377   * @param  enabled  Specifies whether debugging should be enabled.
378   * @param  types    The set of debug types that should be enabled.  It may be
379   *                  {@code null} or empty to indicate that it should be for
380   *                  all debug types.
381   */
382  public static void setEnabled(final boolean enabled,
383                                final Set<DebugType> types)
384  {
385    if ((types == null) || types.isEmpty())
386    {
387      debugTypes = EnumSet.allOf(DebugType.class);
388    }
389    else
390    {
391      debugTypes = EnumSet.copyOf(types);
392    }
393
394    debugEnabled = enabled;
395  }
396
397
398
399  /**
400   * Indicates whether log messages should include a stack trace of the thread
401   * that invoked the debug method.
402   *
403   * @return  {@code true} if log messages should include a stack trace of the
404   *          thread that invoked the debug method, or {@code false} if not.
405   */
406  public static boolean includeStackTrace()
407  {
408    return includeStackTrace;
409  }
410
411
412
413  /**
414   * Specifies whether log messages should include a stack trace of the thread
415   * that invoked the debug method.
416   *
417   * @param  includeStackTrace  Indicates whether log messages should include a
418   *                            stack trace of the thread that invoked the debug
419   *                            method.
420   */
421  public static void setIncludeStackTrace(final boolean includeStackTrace)
422  {
423    Debug.includeStackTrace = includeStackTrace;
424  }
425
426
427
428  /**
429   * Retrieves the set of debug types that will be used if debugging is enabled.
430   *
431   * @return  The set of debug types that will be used if debugging is enabled.
432   */
433  public static EnumSet<DebugType> getDebugTypes()
434  {
435    return debugTypes;
436  }
437
438
439
440  /**
441   * Writes debug information about the provided exception, if appropriate.  If
442   * it is to be logged, then it will be sent to the underlying logger using the
443   * {@code WARNING} level.
444   *
445   * @param  t  The exception for which debug information should be written.
446   */
447  public static void debugException(final Throwable t)
448  {
449    if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION))
450    {
451      debugException(Level.WARNING, t);
452    }
453  }
454
455
456
457  /**
458   * Writes debug information about the provided exception, if appropriate.
459   *
460   * @param  l  The log level that should be used for the debug information.
461   * @param  t  The exception for which debug information should be written.
462   */
463  public static void debugException(final Level l, final Throwable t)
464  {
465    if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION))
466    {
467      final StringBuilder buffer = new StringBuilder();
468      addCommonHeader(buffer, l);
469      buffer.append("caughtException=\"");
470      StaticUtils.getStackTrace(t, buffer);
471      buffer.append('"');
472
473      logger.log(l, buffer.toString(), t);
474    }
475  }
476
477
478
479  /**
480   * Writes debug information to indicate that a connection has been
481   * established, if appropriate.  If it is to be logged, then it will be sent
482   * to the underlying logger using the {@code INFO} level.
483   *
484   * @param  h  The address of the server to which the connection was
485   *            established.
486   * @param  p  The port of the server to which the connection was established.
487   */
488  public static void debugConnect(final String h, final int p)
489  {
490    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
491    {
492      debugConnect(Level.INFO, h, p, null);
493    }
494  }
495
496
497
498  /**
499   * Writes debug information to indicate that a connection has been
500   * established, if appropriate.
501   *
502   * @param  l  The log level that should be used for the debug information.
503   * @param  h  The address of the server to which the connection was
504   *            established.
505   * @param  p  The port of the server to which the connection was established.
506   */
507  public static void debugConnect(final Level l, final String h, final int p)
508  {
509    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
510    {
511      debugConnect(l, h, p, null);
512    }
513  }
514
515
516
517  /**
518   * Writes debug information to indicate that a connection has been
519   * established, if appropriate.  If it is to be logged, then it will be sent
520   * to the underlying logger using the {@code INFO} level.
521   *
522   * @param  h  The address of the server to which the connection was
523   *            established.
524   * @param  p  The port of the server to which the connection was established.
525   * @param  c  The connection object for the connection that has been
526   *            established.  It may be {@code null} for historic reasons, but
527   *            should be non-{@code null} in new uses.
528   */
529  public static void debugConnect(final String h, final int p,
530                                  final LDAPConnection c)
531  {
532    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
533    {
534      debugConnect(Level.INFO, h, p, c);
535    }
536  }
537
538
539
540  /**
541   * Writes debug information to indicate that a connection has been
542   * established, if appropriate.
543   *
544   * @param  l  The log level that should be used for the debug information.
545   * @param  h  The address of the server to which the connection was
546   *            established.
547   * @param  p  The port of the server to which the connection was established.
548   * @param  c  The connection object for the connection that has been
549   *            established.  It may be {@code null} for historic reasons, but
550   *            should be non-{@code null} in new uses.
551   */
552  public static void debugConnect(final Level l, final String h, final int p,
553                                  final LDAPConnection c)
554  {
555    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
556    {
557      final StringBuilder buffer = new StringBuilder();
558      addCommonHeader(buffer, l);
559      buffer.append("connectedTo=\"");
560      buffer.append(h);
561      buffer.append(':');
562      buffer.append(p);
563      buffer.append('"');
564
565      if (c != null)
566      {
567        buffer.append(" connectionID=");
568        buffer.append(c.getConnectionID());
569
570        final String connectionName = c.getConnectionName();
571        if (connectionName != null)
572        {
573          buffer.append(" connectionName=\"");
574          buffer.append(connectionName);
575          buffer.append('"');
576        }
577
578        final String connectionPoolName = c.getConnectionPoolName();
579        if (connectionPoolName != null)
580        {
581          buffer.append(" connectionPoolName=\"");
582          buffer.append(connectionPoolName);
583          buffer.append('"');
584        }
585      }
586
587      logger.log(l, buffer.toString());
588    }
589  }
590
591
592
593  /**
594   * Writes debug information to indicate that a connection has been
595   * terminated, if appropriate.  If it is to be logged, then it will be sent
596   * to the underlying logger using the {@code INFO} level.
597   *
598   * @param  h  The address of the server to which the connection was
599   *            established.
600   * @param  p  The port of the server to which the connection was established.
601   * @param  t  The disconnect type.
602   * @param  m  The disconnect message, if available.
603   * @param  e  The disconnect cause, if available.
604   */
605  public static void debugDisconnect(final String h, final int p,
606                                     final DisconnectType t, final String m,
607                                     final Throwable e)
608  {
609    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
610    {
611      debugDisconnect(Level.INFO, h, p, null, t, m, e);
612    }
613  }
614
615
616
617  /**
618   * Writes debug information to indicate that a connection has been
619   * terminated, if appropriate.
620   *
621   * @param  l  The log level that should be used for the debug information.
622   * @param  h  The address of the server to which the connection was
623   *            established.
624   * @param  p  The port of the server to which the connection was established.
625   * @param  t  The disconnect type.
626   * @param  m  The disconnect message, if available.
627   * @param  e  The disconnect cause, if available.
628   */
629  public static void debugDisconnect(final Level l, final String h, final int p,
630                                     final DisconnectType t, final String m,
631                                     final Throwable e)
632  {
633    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
634    {
635      debugDisconnect(l, h, p, null, t, m, e);
636    }
637  }
638
639
640
641  /**
642   * Writes debug information to indicate that a connection has been
643   * terminated, if appropriate.  If it is to be logged, then it will be sent
644   * to the underlying logger using the {@code INFO} level.
645   *
646   * @param  h  The address of the server to which the connection was
647   *            established.
648   * @param  p  The port of the server to which the connection was established.
649   * @param  c  The connection object for the connection that has been closed.
650   *            It may be {@code null} for historic reasons, but should be
651   *            non-{@code null} in new uses.
652   * @param  t  The disconnect type.
653   * @param  m  The disconnect message, if available.
654   * @param  e  The disconnect cause, if available.
655   */
656  public static void debugDisconnect(final String h, final int p,
657                                     final LDAPConnection c,
658                                     final DisconnectType t, final String m,
659                                     final Throwable e)
660  {
661    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
662    {
663      debugDisconnect(Level.INFO, h, p, c, t, m, e);
664    }
665  }
666
667
668
669  /**
670   * Writes debug information to indicate that a connection has been
671   * terminated, if appropriate.
672   *
673   * @param  l  The log level that should be used for the debug information.
674   * @param  h  The address of the server to which the connection was
675   *            established.
676   * @param  p  The port of the server to which the connection was established.
677   * @param  c  The connection object for the connection that has been closed.
678   *            It may be {@code null} for historic reasons, but should be
679   *            non-{@code null} in new uses.
680   * @param  t  The disconnect type.
681   * @param  m  The disconnect message, if available.
682   * @param  e  The disconnect cause, if available.
683   */
684  public static void debugDisconnect(final Level l, final String h, final int p,
685                                     final LDAPConnection c,
686                                     final DisconnectType t, final String m,
687                                     final Throwable e)
688  {
689    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
690    {
691      final StringBuilder buffer = new StringBuilder();
692      addCommonHeader(buffer, l);
693
694      if (c != null)
695      {
696        buffer.append("connectionID=");
697        buffer.append(c.getConnectionID());
698
699        final String connectionName = c.getConnectionName();
700        if (connectionName != null)
701        {
702          buffer.append(" connectionName=\"");
703          buffer.append(connectionName);
704          buffer.append('"');
705        }
706
707        final String connectionPoolName = c.getConnectionPoolName();
708        if (connectionPoolName != null)
709        {
710          buffer.append(" connectionPoolName=\"");
711          buffer.append(connectionPoolName);
712          buffer.append('"');
713        }
714
715        buffer.append(' ');
716      }
717
718      buffer.append("disconnectedFrom=\"");
719      buffer.append(h);
720      buffer.append(':');
721      buffer.append(p);
722      buffer.append("\" disconnectType=\"");
723      buffer.append(t.name());
724      buffer.append('"');
725
726      if (m != null)
727      {
728        buffer.append("\" disconnectMessage=\"");
729        buffer.append(m);
730        buffer.append('"');
731      }
732
733      if (e != null)
734      {
735        buffer.append("\" disconnectCause=\"");
736        StaticUtils.getStackTrace(e, buffer);
737        buffer.append('"');
738      }
739
740      logger.log(l, buffer.toString(), c);
741    }
742  }
743
744
745
746  /**
747   * Writes debug information about the provided request, if appropriate.  If
748   * it is to be logged, then it will be sent to the underlying logger using the
749   * {@code INFO} level.
750   *
751   * @param  r  The LDAP request for which debug information should be written.
752   */
753  public static void debugLDAPRequest(final LDAPRequest r)
754  {
755    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
756    {
757      debugLDAPRequest(Level.INFO, r, -1, null);
758    }
759  }
760
761
762
763  /**
764   * Writes debug information about the provided request, if appropriate.
765   *
766   * @param  l  The log level that should be used for the debug information.
767   * @param  r  The LDAP request for which debug information should be written.
768   */
769  public static void debugLDAPRequest(final Level l, final LDAPRequest r)
770  {
771    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
772    {
773      debugLDAPRequest(l, r, -1, null);
774    }
775  }
776
777
778
779  /**
780   * Writes debug information about the provided request, if appropriate.  If
781   * it is to be logged, then it will be sent to the underlying logger using the
782   * {@code INFO} level.
783   *
784   * @param  r  The LDAP request for which debug information should be written.
785   * @param  i  The message ID for the request that will be sent.  It may be
786   *            negative if no message ID is available.
787   * @param  c  The connection on which the request will be sent.  It may be
788   *            {@code null} for historic reasons, but should be
789   *            non-{@code null} in new uses.
790   */
791  public static void debugLDAPRequest(final LDAPRequest r, final int i,
792                                      final LDAPConnection c)
793  {
794    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
795    {
796      debugLDAPRequest(Level.INFO, r, i, c);
797    }
798  }
799
800
801
802  /**
803   * Writes debug information about the provided request, if appropriate.
804   *
805   * @param  l  The log level that should be used for the debug information.
806   * @param  r  The LDAP request for which debug information should be written.
807   * @param  i  The message ID for the request that will be sent.  It may be
808   *            negative if no message ID is available.
809   * @param  c  The connection on which the request will be sent.  It may be
810   *            {@code null} for historic reasons, but should be
811   *            non-{@code null} in new uses.
812   */
813  public static void debugLDAPRequest(final Level l, final LDAPRequest r,
814                                      final int i, final LDAPConnection c)
815  {
816    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
817    {
818      debugLDAPRequest(Level.INFO, String.valueOf(r), i, c);
819    }
820  }
821
822
823
824  /**
825   * Writes debug information about the provided request, if appropriate.
826   *
827   * @param  l  The log level that should be used for the debug information.
828   * @param  s  A string representation of the LDAP request for which debug
829   *            information should be written.
830   * @param  i  The message ID for the request that will be sent.  It may be
831   *            negative if no message ID is available.
832   * @param  c  The connection on which the request will be sent.  It may be
833   *            {@code null} for historic reasons, but should be
834   *            non-{@code null} in new uses.
835   */
836  public static void debugLDAPRequest(final Level l, final String s,
837                                      final int i, final LDAPConnection c)
838  {
839    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
840    {
841      final StringBuilder buffer = new StringBuilder();
842      addCommonHeader(buffer, l);
843
844      if (c != null)
845      {
846        buffer.append("connectionID=");
847        buffer.append(c.getConnectionID());
848
849        final String connectionName = c.getConnectionName();
850        if (connectionName != null)
851        {
852          buffer.append(" connectionName=\"");
853          buffer.append(connectionName);
854          buffer.append('"');
855        }
856
857        final String connectionPoolName = c.getConnectionPoolName();
858        if (connectionPoolName != null)
859        {
860          buffer.append(" connectionPoolName=\"");
861          buffer.append(connectionPoolName);
862          buffer.append('"');
863        }
864
865        buffer.append(" connectedTo=\"");
866        buffer.append(c.getConnectedAddress());
867        buffer.append(':');
868        buffer.append(c.getConnectedPort());
869        buffer.append("\" ");
870
871        try
872        {
873          final int soTimeout = InternalSDKHelper.getSoTimeout(c);
874          buffer.append("socketTimeoutMillis=");
875          buffer.append(soTimeout);
876          buffer.append(' ');
877        } catch (final Exception e) {}
878      }
879
880      if (i >= 0)
881      {
882        buffer.append("messageID=");
883        buffer.append(i);
884        buffer.append(' ');
885      }
886
887      buffer.append("sendingLDAPRequest=\"");
888      buffer.append(s);
889      buffer.append('"');
890
891      logger.log(l,  buffer.toString());
892    }
893  }
894
895
896
897  /**
898   * Writes debug information about the provided result, if appropriate.  If
899   * it is to be logged, then it will be sent to the underlying logger using the
900   * {@code INFO} level.
901   *
902   * @param  r  The result for which debug information should be written.
903   */
904  public static void debugLDAPResult(final LDAPResponse r)
905  {
906    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
907    {
908      debugLDAPResult(Level.INFO, r, null);
909    }
910  }
911
912
913
914  /**
915   * Writes debug information about the provided result, if appropriate.
916   *
917   * @param  l  The log level that should be used for the debug information.
918   * @param  r  The result for which debug information should be written.
919   */
920  public static void debugLDAPResult(final Level l, final LDAPResponse r)
921  {
922    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
923    {
924      debugLDAPResult(l, r, null);
925    }
926  }
927
928
929
930  /**
931   * Writes debug information about the provided result, if appropriate.  If
932   * it is to be logged, then it will be sent to the underlying logger using the
933   * {@code INFO} level.
934   *
935   * @param  r  The result for which debug information should be written.
936   * @param  c  The connection on which the response was received.  It may be
937   *            {@code null} for historic reasons, but should be
938   *            non-{@code null} in new uses.
939   */
940  public static void debugLDAPResult(final LDAPResponse r,
941                                     final LDAPConnection c)
942  {
943    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
944    {
945      debugLDAPResult(Level.INFO, r, c);
946    }
947  }
948
949
950
951  /**
952   * Writes debug information about the provided result, if appropriate.
953   *
954   * @param  l  The log level that should be used for the debug information.
955   * @param  r  The result for which debug information should be written.
956   * @param  c  The connection on which the response was received.  It may be
957   *            {@code null} for historic reasons, but should be
958   *            non-{@code null} in new uses.
959   */
960  public static void debugLDAPResult(final Level l, final LDAPResponse r,
961                                     final LDAPConnection c)
962  {
963    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
964    {
965      final StringBuilder buffer = new StringBuilder();
966      addCommonHeader(buffer, l);
967
968      if (c != null)
969      {
970        buffer.append("connectionID=");
971        buffer.append(c.getConnectionID());
972
973        final String connectionName = c.getConnectionName();
974        if (connectionName != null)
975        {
976          buffer.append(" connectionName=\"");
977          buffer.append(connectionName);
978          buffer.append('"');
979        }
980
981        final String connectionPoolName = c.getConnectionPoolName();
982        if (connectionPoolName != null)
983        {
984          buffer.append(" connectionPoolName=\"");
985          buffer.append(connectionPoolName);
986          buffer.append('"');
987        }
988
989        buffer.append(" connectedTo=\"");
990        buffer.append(c.getConnectedAddress());
991        buffer.append(':');
992        buffer.append(c.getConnectedPort());
993        buffer.append("\" ");
994      }
995
996      buffer.append("readLDAPResult=\"");
997      r.toString(buffer);
998      buffer.append('"');
999
1000      logger.log(l,  buffer.toString());
1001    }
1002  }
1003
1004
1005
1006  /**
1007   * Writes debug information about the provided ASN.1 element to be written,
1008   * if appropriate.  If it is to be logged, then it will be sent to the
1009   * underlying logger using the {@code INFO} level.
1010   *
1011   * @param  e  The ASN.1 element for which debug information should be written.
1012   */
1013  public static void debugASN1Write(final ASN1Element e)
1014  {
1015    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1016    {
1017      debugASN1Write(Level.INFO, e);
1018    }
1019  }
1020
1021
1022
1023  /**
1024   * Writes debug information about the provided ASN.1 element to be written,
1025   * if appropriate.
1026   *
1027   * @param  l  The log level that should be used for the debug information.
1028   * @param  e  The ASN.1 element for which debug information should be written.
1029   */
1030  public static void debugASN1Write(final Level l, final ASN1Element e)
1031  {
1032    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1033    {
1034      final StringBuilder buffer = new StringBuilder();
1035      addCommonHeader(buffer, l);
1036      buffer.append("writingASN1Element=\"");
1037      e.toString(buffer);
1038      buffer.append('"');
1039
1040      logger.log(l, buffer.toString());
1041    }
1042  }
1043
1044
1045
1046  /**
1047   * Writes debug information about the provided ASN.1 element to be written,
1048   * if appropriate.  If it is to be logged, then it will be sent to the
1049   * underlying logger using the {@code INFO} level.
1050   *
1051   * @param  b  The ASN.1 buffer with the information to be written.
1052   */
1053  public static void debugASN1Write(final ASN1Buffer b)
1054  {
1055    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1056    {
1057      debugASN1Write(Level.INFO, b);
1058    }
1059  }
1060
1061
1062
1063  /**
1064   * Writes debug information about the provided ASN.1 element to be written,
1065   * if appropriate.
1066   *
1067   * @param  l  The log level that should be used for the debug information.
1068   * @param  b  The ASN1Buffer with the information to be written.
1069   */
1070  public static void debugASN1Write(final Level l, final ASN1Buffer b)
1071  {
1072    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1073    {
1074      final StringBuilder buffer = new StringBuilder();
1075      addCommonHeader(buffer, l);
1076      buffer.append("writingASN1Element=\"");
1077      StaticUtils.toHex(b.toByteArray(), buffer);
1078      buffer.append('"');
1079
1080      logger.log(l, buffer.toString());
1081    }
1082  }
1083
1084
1085
1086  /**
1087   * Writes debug information about the provided ASN.1 element that was read, if
1088   * appropriate.  If it is to be logged, then it will be sent to the underlying
1089   * logger using the {@code INFO} level.
1090   *
1091   * @param  e  The ASN.1 element for which debug information should be written.
1092   */
1093  public static void debugASN1Read(final ASN1Element e)
1094  {
1095    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1096    {
1097      debugASN1Read(Level.INFO, e);
1098    }
1099  }
1100
1101
1102
1103  /**
1104   * Writes debug information about the provided ASN.1 element that was read, if
1105   * appropriate.
1106   *
1107   * @param  l  The log level that should be used for the debug information.
1108   * @param  e  The ASN.1 element for which debug information should be written.
1109   */
1110  public static void debugASN1Read(final Level l, final ASN1Element e)
1111  {
1112    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1113    {
1114      final StringBuilder buffer = new StringBuilder();
1115      addCommonHeader(buffer, l);
1116      buffer.append("readASN1Element=\"");
1117      e.toString(buffer);
1118      buffer.append('"');
1119
1120      logger.log(l, buffer.toString());
1121    }
1122  }
1123
1124
1125
1126  /**
1127   * Writes debug information about the provided ASN.1 element that was read, if
1128   * appropriate.
1129   *
1130   * @param  l         The log level that should be used for the debug
1131   *                   information.
1132   * @param  dataType  A string representation of the data type for the data
1133   *                   that was read.
1134   * @param  berType   The BER type for the element that was read.
1135   * @param  length    The number of bytes in the value of the element that was
1136   *                   read.
1137   * @param  value     A representation of the value that was read.  The debug
1138   *                   message will include the string representation of this
1139   *                   value, unless the value is a byte array in which it will
1140   *                   be a hex representation of the bytes that it contains.
1141   *                   It may be {@code null} for an ASN.1 null element.
1142   */
1143  public static void debugASN1Read(final Level l, final String dataType,
1144                                   final int berType, final int length,
1145                                   final Object value)
1146  {
1147    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1148    {
1149      final StringBuilder buffer = new StringBuilder();
1150      addCommonHeader(buffer, l);
1151      buffer.append("readASN1Element=\"dataType='");
1152      buffer.append(dataType);
1153      buffer.append("' berType='");
1154      buffer.append(StaticUtils.toHex((byte) (berType & 0xFF)));
1155      buffer.append("' valueLength=");
1156      buffer.append(length);
1157
1158      if (value != null)
1159      {
1160        buffer.append(" value='");
1161        if (value instanceof byte[])
1162        {
1163          StaticUtils.toHex((byte[]) value, buffer);
1164        }
1165        else
1166        {
1167          buffer.append(value);
1168        }
1169        buffer.append('\'');
1170      }
1171      buffer.append('"');
1172
1173      logger.log(l, buffer.toString());
1174    }
1175  }
1176
1177
1178
1179  /**
1180   * Writes debug information about interaction with a connection pool.
1181   *
1182   * @param  l  The log level that should be used for the debug information.
1183   * @param  p  The associated connection pool.
1184   * @param  c  The associated LDAP connection, if appropriate.
1185   * @param  m  A message with information about the pool interaction.
1186   * @param  e  An exception to include with the log message, if appropriate.
1187   */
1188  public static void debugConnectionPool(final Level l,
1189                                         final AbstractConnectionPool p,
1190                                         final LDAPConnection c, final String m,
1191                                         final Throwable e)
1192  {
1193    if (debugEnabled && debugTypes.contains(DebugType.CONNECTION_POOL))
1194    {
1195      final StringBuilder buffer = new StringBuilder();
1196      addCommonHeader(buffer, l);
1197
1198      final String poolName = p.getConnectionPoolName();
1199      buffer.append("connectionPool=\"");
1200      if (poolName == null)
1201      {
1202        buffer.append("{unnamed}");
1203      }
1204      else
1205      {
1206        buffer.append(poolName);
1207      }
1208      buffer.append("\" ");
1209
1210      if (c != null)
1211      {
1212        buffer.append(" connectionID=");
1213        buffer.append(c.getConnectionID());
1214
1215        final String hostPort = c.getHostPort();
1216        if ((hostPort != null) && (! hostPort.isEmpty()))
1217        {
1218          buffer.append(" connectedTo=\"");
1219          buffer.append(hostPort);
1220          buffer.append('"');
1221        }
1222      }
1223
1224      final long currentAvailable = p.getCurrentAvailableConnections();
1225      if (currentAvailable >= 0)
1226      {
1227        buffer.append(" currentAvailableConnections=");
1228        buffer.append(currentAvailable);
1229      }
1230
1231      final long maxAvailable = p.getMaximumAvailableConnections();
1232      if (maxAvailable >= 0)
1233      {
1234        buffer.append(" maxAvailableConnections=");
1235        buffer.append(maxAvailable);
1236      }
1237
1238      buffer.append(" message=\"");
1239      buffer.append(m);
1240      buffer.append('"');
1241
1242      if (e != null)
1243      {
1244        buffer.append(" exception=\"");
1245        StaticUtils.getStackTrace(e, buffer);
1246        buffer.append('"');
1247      }
1248
1249      logger.log(l, buffer.toString(), e);
1250    }
1251  }
1252
1253
1254
1255  /**
1256   * Writes debug information about the provided LDIF record to be written, if
1257   * if appropriate.  If it is to be logged, then it will be sent to the
1258   * underlying logger using the {@code INFO} level.
1259   *
1260   * @param  r  The LDIF record for which debug information should be written.
1261   */
1262  public static void debugLDIFWrite(final LDIFRecord r)
1263  {
1264    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1265    {
1266      debugLDIFWrite(Level.INFO, r);
1267    }
1268  }
1269
1270
1271
1272  /**
1273   * Writes debug information about the provided LDIF record to be written, if
1274   * appropriate.
1275   *
1276   * @param  l  The log level that should be used for the debug information.
1277   * @param  r  The LDIF record for which debug information should be written.
1278   */
1279  public static void debugLDIFWrite(final Level l, final LDIFRecord r)
1280  {
1281    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1282    {
1283      final StringBuilder buffer = new StringBuilder();
1284      addCommonHeader(buffer, l);
1285      buffer.append("writingLDIFRecord=\"");
1286      r.toString(buffer);
1287      buffer.append('"');
1288
1289      logger.log(l, buffer.toString());
1290    }
1291  }
1292
1293
1294
1295  /**
1296   * Writes debug information about the provided record read from LDIF, if
1297   * appropriate.  If it is to be logged, then it will be sent to the underlying
1298   * logger using the {@code INFO} level.
1299   *
1300   * @param  r  The LDIF record for which debug information should be written.
1301   */
1302  public static void debugLDIFRead(final LDIFRecord r)
1303  {
1304    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1305    {
1306      debugLDIFRead(Level.INFO, r);
1307    }
1308  }
1309
1310
1311
1312  /**
1313   * Writes debug information about the provided record read from LDIF, if
1314   * appropriate.
1315   *
1316   * @param  l  The log level that should be used for the debug information.
1317   * @param  r  The LDIF record for which debug information should be written.
1318   */
1319  public static void debugLDIFRead(final Level l, final LDIFRecord r)
1320  {
1321    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1322    {
1323      final StringBuilder buffer = new StringBuilder();
1324      addCommonHeader(buffer, l);
1325      buffer.append("readLDIFRecord=\"");
1326      r.toString(buffer);
1327      buffer.append('"');
1328
1329      logger.log(l, buffer.toString());
1330    }
1331  }
1332
1333
1334
1335  /**
1336   * Writes debug information about monitor entry parsing.  If it is to be
1337   * logged, then it will be sent to the underlying logger using the
1338   * {@code FINE} level.
1339   *
1340   * @param  e  The entry containing the monitor information being parsed.
1341   * @param  m  The message to be written to the debug logger.
1342   */
1343  public static void debugMonitor(final Entry e, final String m)
1344  {
1345    if (debugEnabled && debugTypes.contains(DebugType.MONITOR))
1346    {
1347      debugMonitor(Level.FINE, e, m);
1348    }
1349  }
1350
1351
1352
1353  /**
1354   * Writes debug information about monitor entry parsing, if appropriate.
1355   *
1356   * @param  l  The log level that should be used for the debug information.
1357   * @param  e  The entry containing the monitor information being parsed.
1358   * @param  m  The message to be written to the debug logger.
1359   */
1360  public static void debugMonitor(final Level l, final Entry e, final String m)
1361  {
1362    if (debugEnabled && debugTypes.contains(DebugType.MONITOR))
1363    {
1364      final StringBuilder buffer = new StringBuilder();
1365      addCommonHeader(buffer, l);
1366      buffer.append("monitorEntryDN=\"");
1367      buffer.append(e.getDN());
1368      buffer.append("\" message=\"");
1369      buffer.append(m);
1370      buffer.append('"');
1371
1372      logger.log(l, buffer.toString());
1373    }
1374  }
1375
1376
1377
1378  /**
1379   * Writes debug information about a coding error detected in the use of the
1380   * LDAP SDK.  If it is to be logged, then it will be sent to the underlying
1381   * logger using the {@code SEVERE} level.
1382   *
1383   * @param  t  The {@code Throwable} object that was created and will be thrown
1384   *            as a result of the coding error.
1385   */
1386  public static void debugCodingError(final Throwable t)
1387  {
1388    if (debugEnabled && debugTypes.contains(DebugType.CODING_ERROR))
1389    {
1390      final StringBuilder buffer = new StringBuilder();
1391      addCommonHeader(buffer, Level.SEVERE);
1392      buffer.append("codingError=\"");
1393      StaticUtils.getStackTrace(t, buffer);
1394      buffer.append('"');
1395
1396      logger.log(Level.SEVERE, buffer.toString());
1397    }
1398  }
1399
1400
1401
1402  /**
1403   * Writes a generic debug message, if appropriate.
1404   *
1405   * @param  l  The log level that should be used for the debug information.
1406   * @param  t  The debug type to use to determine whether to write the message.
1407   * @param  m  The message to be written.
1408   */
1409  public static void debug(final Level l, final DebugType t, final String m)
1410  {
1411    if (debugEnabled && debugTypes.contains(t))
1412    {
1413      final StringBuilder buffer = new StringBuilder();
1414      addCommonHeader(buffer, l);
1415      buffer.append("message=\"");
1416      buffer.append(m);
1417      buffer.append('"');
1418
1419      logger.log(l, buffer.toString());
1420    }
1421  }
1422
1423
1424
1425  /**
1426   * Writes a generic debug message, if appropriate.
1427   *
1428   * @param  l  The log level that should be used for the debug information.
1429   * @param  t  The debug type to use to determine whether to write the message.
1430   * @param  m  The message to be written.
1431   * @param  e  An exception to include with the log message.
1432   */
1433  public static void debug(final Level l, final DebugType t, final String m,
1434                           final Throwable e)
1435  {
1436    if (debugEnabled && debugTypes.contains(t))
1437    {
1438      final StringBuilder buffer = new StringBuilder();
1439      addCommonHeader(buffer, l);
1440      buffer.append("message=\"");
1441      buffer.append(m);
1442      buffer.append('"');
1443      buffer.append(" exception=\"");
1444      StaticUtils.getStackTrace(e, buffer);
1445      buffer.append('"');
1446
1447      logger.log(l, buffer.toString(), e);
1448    }
1449  }
1450
1451
1452
1453  /**
1454   * Writes common header information to the provided buffer.  It will include
1455   * the thread ID, name, and caller stack trace (optional), and it will be
1456   * followed by a trailing space.
1457   *
1458   * @param  buffer  The buffer to which the information should be appended.
1459   * @param  level   The log level for the message that will be written.
1460   */
1461  private static void addCommonHeader(final StringBuilder buffer,
1462                                      final Level level)
1463  {
1464    buffer.append("level=\"");
1465    buffer.append(level.getName());
1466    buffer.append("\" threadID=");
1467    buffer.append(Thread.currentThread().getId());
1468    buffer.append(" threadName=\"");
1469    buffer.append(Thread.currentThread().getName());
1470
1471    if (includeStackTrace)
1472    {
1473      buffer.append("\" calledFrom=\"");
1474
1475      boolean appended   = false;
1476      boolean foundDebug = false;
1477      for (final StackTraceElement e : Thread.currentThread().getStackTrace())
1478      {
1479        final String className = e.getClassName();
1480        if (className.equals(Debug.class.getName()))
1481        {
1482          foundDebug = true;
1483        }
1484        else if (foundDebug)
1485        {
1486          if (appended)
1487          {
1488            buffer.append(" / ");
1489          }
1490          appended = true;
1491
1492          buffer.append(e.getMethodName());
1493          buffer.append('(');
1494          buffer.append(e.getFileName());
1495
1496          final int lineNumber = e.getLineNumber();
1497          if (lineNumber > 0)
1498          {
1499            buffer.append(':');
1500            buffer.append(lineNumber);
1501          }
1502          else if (e.isNativeMethod())
1503          {
1504            buffer.append(":native");
1505          }
1506
1507          buffer.append(')');
1508        }
1509      }
1510    }
1511
1512    buffer.append("\" ldapSDKVersion=\"");
1513    buffer.append(Version.NUMERIC_VERSION_STRING);
1514    buffer.append("\" revision=\"");
1515    buffer.append(Version.REVISION_ID);
1516    buffer.append("\" ");
1517  }
1518}