/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;



import java.util.ArrayList;

import org.opends.messages.Message;
import org.opends.server.admin.std.server.MonitorProviderCfg;
import org.opends.server.api.MonitorProvider;
import org.opends.server.core.DirectoryServer;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DebugLogLevel;

import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;



/**
 * This class defines a data structure that will be used to keep track of
 * various metrics related to LDAP communication that the server has conducted.
 * The statistics that will be tracked include:
 *
 * <UL>
 *   <LI>The total number of LDAP client connections accepted by the
 *       server.</LI>
 *   <LI>The total number of LDAP client connections that have been closed.</LI>
 *   <LI>The total number of LDAP messages read, both overall and broken down
 *       by message type.</LI>
 *   <LI>The total number of LDAP messages written, both overall and broken down
 *       by message type.</LI>
 *   <LI>The total number of bytes read from LDAP clients.</LI>
 *   <LI>The total number of bytes written to LDAP clients.</LI>
 * </UL>
 *
 * <BR><BR>
 * This class may also be used in a hierarchical form if it is desirable to
 * get specific and general statistics at the same time (e.g., information
 * about the interaction with a specific client or aggregated for all clients).
 */
public class LDAPStatistics
       extends MonitorProvider<MonitorProviderCfg>
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();



  // The statistics maintained by this class.
  private long abandonRequests;
  private long addRequests;
  private long addResponses;
  private long bindRequests;
  private long bindResponses;
  private long bytesRead;
  private long bytesWritten;
  private long compareRequests;
  private long compareResponses;
  private long connectionsClosed;
  private long connectionsEstablished;
  private long deleteRequests;
  private long deleteResponses;
  private long extendedRequests;
  private long extendedResponses;
  private long messagesRead;
  private long messagesWritten;
  private long modifyRequests;
  private long modifyResponses;
  private long modifyDNRequests;
  private long modifyDNResponses;
  private long operationsAbandoned;
  private long operationsCompleted;
  private long operationsInitiated;
  private long searchRequests;
  private long searchResultEntries;
  private long searchResultReferences;
  private long searchResultsDone;
  private long unbindRequests;

  // The parent that should also be updated whenever statistics in this object
  // are updated.
  private LDAPStatistics parent;

  // The locks used to provide threadsafe access to this class.  In this case,
  // read and write refer to the type of LDAP communication, not access to the
  // protected data.
  private Object abandonLock;
  private Object connectLock;
  private Object disconnectLock;
  private Object readLock;
  private Object writeLock;

  // The instance name for this monitor provider instance.
  private String instanceName;




  /**
   * Creates a new instance of this class with no parent.
   *
   * @param  instanceName  The name for this monitor provider instance.
   */
  public LDAPStatistics(String instanceName)
  {
    this(instanceName, null);

    DirectoryServer.registerMonitorProvider(this);
  }



  /**
   * Creates a new instance of this class with the specified parent.
   *
   * @param  instanceName  The name for this monitor provider instance.
   * @param  parent        The parent object that should also be updated
   *                       whenever this class is updated.  It may be null if
   *                       there should not be a parent.
   */
  public LDAPStatistics(String instanceName, LDAPStatistics parent)
  {
    super("LDAP Statistics Monitor Provider");


    this.instanceName = instanceName;
    this.parent       = parent;

    abandonLock    = new Object();
    connectLock    = new Object();
    disconnectLock = new Object();
    readLock       = new Object();
    writeLock      = new Object();

    abandonRequests        = 0;
    addRequests            = 0;
    addResponses           = 0;
    bindRequests           = 0;
    bindResponses          = 0;
    bytesRead              = 0;
    bytesWritten           = 0;
    compareRequests        = 0;
    compareResponses       = 0;
    connectionsClosed      = 0;
    connectionsEstablished = 0;
    deleteRequests         = 0;
    deleteResponses        = 0;
    extendedRequests       = 0;
    extendedResponses      = 0;
    messagesRead           = 0;
    messagesWritten        = 0;
    modifyRequests         = 0;
    modifyResponses        = 0;
    modifyDNRequests       = 0;
    modifyDNResponses      = 0;
    operationsAbandoned    = 0;
    operationsCompleted    = 0;
    operationsInitiated    = 0;
    searchRequests         = 0;
    searchResultEntries    = 0;
    searchResultReferences = 0;
    searchResultsDone      = 0;
    unbindRequests         = 0;
  }



  /**
   * {@inheritDoc}
   */
  public void initializeMonitorProvider(MonitorProviderCfg configuration)
         throws ConfigException
  {
    // Throw an exception, because this monitor is not intended to be
    // dynamically loaded from the configuration.  Rather, it should be
    // explicitly created and registered by the LDAP connection handler or an
    // LDAP client connection.
    Message message = ERR_LDAP_STATS_INVALID_MONITOR_INITIALIZATION.get(
        String.valueOf(configuration.dn()));
    throw new ConfigException(message);
  }



  /**
   * Retrieves the name of this monitor provider.  It should be unique among all
   * monitor providers, including all instances of the same monitor provider.
   *
   * @return  The name of this monitor provider.
   */
  public String getMonitorInstanceName()
  {
    return instanceName;
  }



  /**
   * Retrieves the length of time in milliseconds that should elapse between
   * calls to the <CODE>updateMonitorData()</CODE> method.  A negative or zero
   * return value indicates that the <CODE>updateMonitorData()</CODE> method
   * should not be periodically invoked.
   *
   * @return  The length of time in milliseconds that should elapse between
   *          calls to the <CODE>updateMonitorData()</CODE> method.
   */
  public long getUpdateInterval()
  {
    // This monitor should not run periodically.
    return -1;
  }



  /**
   * Performs any processing periodic processing that may be desired to update
   * the information associated with this monitor.  Note that best-effort
   * attempts will be made to ensure that calls to this method come
   * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
   * be made.
   */
  public void updateMonitorData()
  {
    // No implementation is required since this does not do periodic updates.
  }



  /**
   * Retrieves a set of attributes containing monitor data that should be
   * returned to the client if the corresponding monitor entry is requested.
   *
   * @return  A set of attributes containing monitor data that should be
   *          returned to the client if the corresponding monitor entry is
   *          requested.
   */
  public ArrayList<Attribute> getMonitorData()
  {
    ArrayList<Attribute> attrs = new ArrayList<Attribute>(29);

    long tmpAbandonRequests;
    long tmpAddRequests;
    long tmpAddResponses;
    long tmpBindRequests;
    long tmpBindResponses;
    long tmpBytesRead;
    long tmpBytesWritten;
    long tmpCompareRequests;
    long tmpCompareResponses;
    long tmpConnectionsClosed;
    long tmpConnectionsEstablished;
    long tmpDeleteRequests;
    long tmpDeleteResponses;
    long tmpExtendedRequests;
    long tmpExtendedResponses;
    long tmpMessagesRead;
    long tmpMessagesWritten;
    long tmpModifyRequests;
    long tmpModifyResponses;
    long tmpModifyDNRequests;
    long tmpModifyDNResponses;
    long tmpOperationsAbandoned;
    long tmpOperationsCompleted;
    long tmpOperationsInitiated;
    long tmpSearchRequests;
    long tmpSearchEntries;
    long tmpSearchReferences;
    long tmpSearchResultsDone;
    long tmpUnbindRequests;

    // Quickly grab the locks and store consistent copies of the information.
    // Note that when grabbing multiple locks, it is essential that they are all
    // acquired in the same order to prevent deadlocks.
    synchronized (abandonLock)
    {
      synchronized (connectLock)
      {
        synchronized (disconnectLock)
        {
          synchronized (writeLock)
          {
            synchronized (readLock)
            {
              tmpAbandonRequests        = abandonRequests;
              tmpAddRequests            = addRequests;
              tmpAddResponses           = addResponses;
              tmpBindRequests           = bindRequests;
              tmpBindResponses          = bindResponses;
              tmpBytesRead              = bytesRead;
              tmpBytesWritten           = bytesWritten;
              tmpCompareRequests        = compareRequests;
              tmpCompareResponses       = compareResponses;
              tmpConnectionsClosed      = connectionsClosed;
              tmpConnectionsEstablished = connectionsEstablished;
              tmpDeleteRequests         = deleteRequests;
              tmpDeleteResponses        = deleteResponses;
              tmpExtendedRequests       = extendedRequests;
              tmpExtendedResponses      = extendedResponses;
              tmpMessagesRead           = messagesRead;
              tmpMessagesWritten        = messagesWritten;
              tmpModifyRequests         = modifyRequests;
              tmpModifyResponses        = modifyResponses;
              tmpModifyDNRequests       = modifyDNRequests;
              tmpModifyDNResponses      = modifyDNResponses;
              tmpOperationsAbandoned    = operationsAbandoned;
              tmpOperationsCompleted    = operationsCompleted;
              tmpOperationsInitiated    = operationsInitiated;
              tmpSearchRequests         = searchRequests;
              tmpSearchEntries          = searchResultEntries;
              tmpSearchReferences       = searchResultReferences;
              tmpSearchResultsDone      = searchResultsDone;
              tmpUnbindRequests         = unbindRequests;
            }
          }
        }
      }
    }


    // Construct the list of attributes to return.
    attrs.add(createAttribute("connectionsEstablished",
                              String.valueOf(tmpConnectionsEstablished)));
    attrs.add(createAttribute("connectionsClosed",
                              String.valueOf(tmpConnectionsClosed)));
    attrs.add(createAttribute("bytesRead", String.valueOf(tmpBytesRead)));
    attrs.add(createAttribute("bytesWritten", String.valueOf(tmpBytesWritten)));
    attrs.add(createAttribute("ldapMessagesRead",
                              String.valueOf(tmpMessagesRead)));
    attrs.add(createAttribute("ldapMessagesWritten",
                              String.valueOf(tmpMessagesWritten)));
    attrs.add(createAttribute("operationsAbandoned",
                              String.valueOf(tmpOperationsAbandoned)));
    attrs.add(createAttribute("operationsInitiated",
                              String.valueOf(tmpOperationsInitiated)));
    attrs.add(createAttribute("operationsCompleted",
                              String.valueOf(tmpOperationsCompleted)));
    attrs.add(createAttribute("abandonRequests",
                              String.valueOf(tmpAbandonRequests)));
    attrs.add(createAttribute("addRequests", String.valueOf(tmpAddRequests)));
    attrs.add(createAttribute("addResponses", String.valueOf(tmpAddResponses)));
    attrs.add(createAttribute("bindRequests", String.valueOf(tmpBindRequests)));
    attrs.add(createAttribute("bindResponses",
                              String.valueOf(tmpBindResponses)));
    attrs.add(createAttribute("compareRequests",
                              String.valueOf(tmpCompareRequests)));
    attrs.add(createAttribute("compareResponses",
                              String.valueOf(tmpCompareResponses)));
    attrs.add(createAttribute("deleteRequests",
                              String.valueOf(tmpDeleteRequests)));
    attrs.add(createAttribute("deleteResponses",
                              String.valueOf(tmpDeleteResponses)));
    attrs.add(createAttribute("extendedRequests",
                              String.valueOf(tmpExtendedRequests)));
    attrs.add(createAttribute("extendedResponses",
                              String.valueOf(tmpExtendedResponses)));
    attrs.add(createAttribute("modifyRequests",
                              String.valueOf(tmpModifyRequests)));
    attrs.add(createAttribute("modifyResponses",
                              String.valueOf(tmpModifyResponses)));
    attrs.add(createAttribute("modifyDNRequests",
                              String.valueOf(tmpModifyDNRequests)));
    attrs.add(createAttribute("modifyDNResponses",
                              String.valueOf(tmpModifyDNResponses)));
    attrs.add(createAttribute("searchRequests",
                              String.valueOf(tmpSearchRequests)));
    attrs.add(createAttribute("searchResultEntries",
                              String.valueOf(tmpSearchEntries)));
    attrs.add(createAttribute("searchResultReferences",
                              String.valueOf(tmpSearchReferences)));
    attrs.add(createAttribute("searchResultsDone",
                              String.valueOf(tmpSearchResultsDone)));
    attrs.add(createAttribute("unbindRequests",
                              String.valueOf(tmpUnbindRequests)));

    return attrs;
  }



  /**
   * Clears any statistical information collected to this point.
   */
  public void clearStatistics()
  {
    // Quickly grab the locks and store consistent copies of the information.
    // Note that when grabbing multiple locks, it is essential that they are all
    // acquired in the same order to prevent deadlocks.
    synchronized (abandonLock)
    {
      synchronized (connectLock)
      {
        synchronized (disconnectLock)
        {
          synchronized (writeLock)
          {
            synchronized (readLock)
            {
              abandonRequests        = 0;
              addRequests            = 0;
              addResponses           = 0;
              bindRequests           = 0;
              bindResponses          = 0;
              bytesRead              = 0;
              bytesWritten           = 0;
              compareRequests        = 0;
              compareResponses       = 0;
              connectionsClosed      = 0;
              connectionsEstablished = 0;
              deleteRequests         = 0;
              deleteResponses        = 0;
              extendedRequests       = 0;
              extendedResponses      = 0;
              messagesRead           = 0;
              messagesWritten        = 0;
              modifyRequests         = 0;
              modifyResponses        = 0;
              modifyDNRequests       = 0;
              modifyDNResponses      = 0;
              operationsAbandoned    = 0;
              operationsCompleted    = 0;
              operationsInitiated    = 0;
              searchRequests         = 0;
              searchResultEntries    = 0;
              searchResultReferences = 0;
              searchResultsDone      = 0;
              unbindRequests         = 0;
            }
          }
        }
      }
    }
  }



  /**
   * Updates the appropriate set of counters to indicate that a new connection
   * has been established.
   */
  public void updateConnect()
  {
    synchronized (connectLock)
    {
      connectionsEstablished++;
    }

    // Update the parent if there is one.
    if (parent != null)
    {
      parent.updateConnect();
    }
  }



  /**
   * Updates the appropriate set of counters to indicate that a connection has
   * been closed.
   */
  public void updateDisconnect()
  {
    synchronized (disconnectLock)
    {
      connectionsClosed++;
    }

    // Update the parent if there is one.
    if (parent != null)
    {
      parent.updateDisconnect();
    }
  }



  /**
   * Updates the appropriate set of counters to indicate that the specified
   * number of bytes have been read by the client.
   *
   * @param  bytesRead  The number of bytes read by the client.
   */
  public void updateBytesRead(int bytesRead)
  {
    synchronized (readLock)
    {
      this.bytesRead += bytesRead;
    }

    // Update the parent if there is one.
    if (parent != null)
    {
      parent.updateBytesRead(bytesRead);
    }
  }



  /**
   * Updates the appropriate set of counters based on the provided message that
   * has been read from the client.
   *
   * @param  message  The message that was read from the client.
   */
  public void updateMessageRead(LDAPMessage message)
  {
    synchronized (readLock)
    {
      messagesRead++;
      operationsInitiated++;

      switch (message.getProtocolOp().getType())
      {
        case OP_TYPE_ABANDON_REQUEST:
          abandonRequests++;
          break;
        case OP_TYPE_ADD_REQUEST:
          addRequests++;
          break;
        case OP_TYPE_BIND_REQUEST:
          bindRequests++;
          break;
        case OP_TYPE_COMPARE_REQUEST:
          compareRequests++;
          break;
        case OP_TYPE_DELETE_REQUEST:
          deleteRequests++;
          break;
        case OP_TYPE_EXTENDED_REQUEST:
          extendedRequests++;
          break;
        case OP_TYPE_MODIFY_REQUEST:
          modifyRequests++;
          break;
        case OP_TYPE_MODIFY_DN_REQUEST:
          modifyDNRequests++;
          break;
        case OP_TYPE_SEARCH_REQUEST:
          searchRequests++;
          break;
        case OP_TYPE_UNBIND_REQUEST:
          unbindRequests++;
          break;
      }
    }

    // Update the parent if there is one.
    if (parent != null)
    {
      parent.updateMessageRead(message);
    }
  }



  /**
   * Updates the appropriate set of counters based on the provided message that
   * has been written to the client.
   *
   * @param  message       The message that was written to the client.
   * @param  bytesWritten  The size of the message written in bytes.
   */
  public void updateMessageWritten(LDAPMessage message, int bytesWritten)
  {
    synchronized (writeLock)
    {
      this.bytesWritten += bytesWritten;
      messagesWritten++;

      switch (message.getProtocolOp().getType())
      {
        case OP_TYPE_ADD_RESPONSE:
          addResponses++;
          operationsCompleted++;
          break;
        case OP_TYPE_BIND_RESPONSE:
          bindResponses++;
          operationsCompleted++;
          break;
        case OP_TYPE_COMPARE_RESPONSE:
          compareResponses++;
          operationsCompleted++;
          break;
        case OP_TYPE_DELETE_RESPONSE:
          deleteResponses++;
          operationsCompleted++;
          break;
        case OP_TYPE_EXTENDED_RESPONSE:
          extendedResponses++;

          // We don't want to include unsolicited notifications as "completed"
          // operations.
          if (message.getMessageID() > 0)
          {
            operationsCompleted++;
          }
          break;
        case OP_TYPE_MODIFY_RESPONSE:
          modifyResponses++;
          operationsCompleted++;
          break;
        case OP_TYPE_MODIFY_DN_RESPONSE:
          modifyDNResponses++;
          operationsCompleted++;
          break;
        case OP_TYPE_SEARCH_RESULT_ENTRY:
          searchResultEntries++;
          break;
        case OP_TYPE_SEARCH_RESULT_REFERENCE:
          searchResultReferences++;
          break;
        case OP_TYPE_SEARCH_RESULT_DONE:
          searchResultsDone++;
          operationsCompleted++;
          break;
      }
    }

    // Update the parent if there is one.
    if (parent != null)
    {
      parent.updateMessageWritten(message, bytesWritten);
    }
  }



  /**
   * Updates the appropriate set of counters to indicate that an operation was
   * abandoned without sending a response to the client.
   */
  public void updateAbandonedOperation()
  {
    synchronized (abandonLock)
    {
      operationsAbandoned++;
    }

    // Update the parent if there is one.
    if (parent != null)
    {
      parent.updateAbandonedOperation();
    }
  }



  /**
   * Constructs an attribute using the provided information.  It will have the
   * default syntax.
   *
   * @param  name   The name to use for the attribute.
   * @param  value  The value to use for the attribute.
   *
   * @return  the constructed attribute.
   */
  private Attribute createAttribute(String name, String value)
  {
    AttributeType attrType = DirectoryServer.getDefaultAttributeType(name);

    ASN1OctetString encodedValue = new ASN1OctetString(value);
    AttributeBuilder builder = new AttributeBuilder(attrType, name);
    try
    {
      builder.add(new AttributeValue(encodedValue, attrType
          .normalize(encodedValue)));
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      builder.add(new AttributeValue(encodedValue, encodedValue));
    }

    return builder.toAttribute();
  }



  /**
   * Retrieves the number of client connections that have been
   * established.
   *
   * @return The number of client connections that have been
   *         established.
   */
  public long getConnectionsEstablished()
  {
    synchronized (connectLock)
    {
      return connectionsEstablished;
    }
  }



  /**
   * Retrieves the number of client connections that have been closed.
   *
   * @return  The number of client connections that have been closed.
   */
  public long getConnectionsClosed()
  {
    synchronized (disconnectLock)
    {
      return connectionsClosed;
    }
  }



  /**
   * Retrieves the number of bytes that have been received from clients.
   *
   * @return  The number of bytes that have been received from clients.
   */
  public long getBytesRead()
  {
    synchronized (readLock)
    {
      return bytesRead;
    }
  }



  /**
   * Retrieves the number of bytes that have been written to clients.
   *
   * @return  The number of bytes that have been written to clients.
   */
  public long getBytesWritten()
  {
    synchronized (writeLock)
    {
      return bytesWritten;
    }
  }



  /**
   * Retrieves the number of LDAP messages that have been received from clients.
   *
   * @return  The number of LDAP messages that have been received from clients.
   */
  public long getMessagesRead()
  {
    synchronized (readLock)
    {
      return messagesRead;
    }
  }



  /**
   * Retrieves the number of LDAP messages that have been written to clients.
   *
   * @return  The number of LDAP messages that have been written to clients.
   */
  public long getMessagesWritten()
  {
    synchronized (writeLock)
    {
      return messagesWritten;
    }
  }



  /**
   * Retrieves the number of operations that have been initiated by clients.
   *
   * @return  The number of operations that have been initiated by clients.
   */
  public long getOperationsInitiated()
  {
    synchronized (readLock)
    {
      return operationsInitiated;
    }
  }



  /**
   * Retrieves the number of operations for which the server has completed
   * processing.
   *
   * @return  The number of operations for which the server has completed
   *          processing.
   */
  public long getOperationsCompleted()
  {
    synchronized (writeLock)
    {
      return operationsCompleted;
    }
  }



  /**
   * Retrieves the number of operations that have been abandoned by clients.
   *
   * @return  The number of operations that have been abandoned by clients.
   */
  public long getOperationsAbandoned()
  {
    synchronized (abandonLock)
    {
      return operationsAbandoned;
    }
  }



  /**
   * Retrieves the number of abandon requests that have been received.
   *
   * @return  The number of abandon requests that have been received.
   */
  public long getAbandonRequests()
  {
    synchronized (readLock)
    {
      return abandonRequests;
    }
  }



  /**
   * Retrieves the number of add requests that have been received.
   *
   * @return  The number of add requests that have been received.
   */
  public long getAddRequests()
  {
    synchronized (readLock)
    {
      return addRequests;
    }
  }



  /**
   * Retrieves the number of add responses that have been sent.
   *
   * @return  The number of add responses that have been sent.
   */
  public long getAddResponses()
  {
    synchronized (writeLock)
    {
      return addResponses;
    }
  }



  /**
   * Retrieves the number of bind requests that have been received.
   *
   * @return  The number of bind requests that have been received.
   */
  public long getBindRequests()
  {
    synchronized (readLock)
    {
      return bindRequests;
    }
  }



  /**
   * Retrieves the number of bind responses that have been sent.
   *
   * @return  The number of bind responses that have been sent.
   */
  public long getBindResponses()
  {
    synchronized (writeLock)
    {
      return bindResponses;
    }
  }



  /**
   * Retrieves the number of compare requests that have been received.
   *
   * @return  The number of compare requests that have been received.
   */
  public long getCompareRequests()
  {
    synchronized (readLock)
    {
      return compareRequests;
    }
  }



  /**
   * Retrieves the number of compare responses that have been sent.
   *
   * @return  The number of compare responses that have been sent.
   */
  public long getCompareResponses()
  {
    synchronized (writeLock)
    {
      return compareResponses;
    }
  }



  /**
   * Retrieves the number of delete requests that have been received.
   *
   * @return  The number of delete requests that have been received.
   */
  public long getDeleteRequests()
  {
    synchronized (readLock)
    {
      return deleteRequests;
    }
  }



  /**
   * Retrieves the number of delete responses that have been sent.
   *
   * @return  The number of delete responses that have been sent.
   */
  public long getDeleteResponses()
  {
    synchronized (writeLock)
    {
      return deleteResponses;
    }
  }



  /**
   * Retrieves the number of extended requests that have been received.
   *
   * @return  The number of extended requests that have been received.
   */
  public long getExtendedRequests()
  {
    synchronized (readLock)
    {
      return extendedRequests;
    }
  }



  /**
   * Retrieves the number of extended responses that have been sent.
   *
   * @return  The number of extended responses that have been sent.
   */
  public long getExtendedResponses()
  {
    synchronized (writeLock)
    {
      return extendedResponses;
    }
  }



  /**
   * Retrieves the number of modify requests that have been received.
   *
   * @return  The number of modify requests that have been received.
   */
  public long getModifyRequests()
  {
    synchronized (readLock)
    {
      return modifyRequests;
    }
  }



  /**
   * Retrieves the number of modify responses that have been sent.
   *
   * @return  The number of modify responses that have been sent.
   */
  public long getModifyResponses()
  {
    synchronized (writeLock)
    {
      return modifyResponses;
    }
  }



  /**
   * Retrieves the number of modify DN requests that have been received.
   *
   * @return  The number of modify DN requests that have been received.
   */
  public long getModifyDNRequests()
  {
    synchronized (readLock)
    {
      return modifyDNRequests;
    }
  }



  /**
   * Retrieves the number of modify DN responses that have been sent.
   *
   * @return  The number of modify DN responses that have been sent.
   */
  public long getModifyDNResponses()
  {
    synchronized (writeLock)
    {
      return modifyDNResponses;
    }
  }



  /**
   * Retrieves the number of search requests that have been received.
   *
   * @return  The number of search requests that have been received.
   */
  public long getSearchRequests()
  {
    synchronized (readLock)
    {
      return searchRequests;
    }
  }



  /**
   * Retrieves the number of search result entries that have been sent.
   *
   * @return  The number of search result entries that have been sent.
   */
  public long getSearchResultEntries()
  {
    synchronized (writeLock)
    {
      return searchResultEntries;
    }
  }



  /**
   * Retrieves the number of search result references that have been sent.
   *
   * @return  The number of search result references that have been sent.
   */
  public long getSearchResultReferences()
  {
    synchronized (writeLock)
    {
      return searchResultReferences;
    }
  }



  /**
   * Retrieves the number of search result done messages that have been sent.
   *
   * @return  The number of search result done messages that have been sent.
   */
  public long getSearchResultsDone()
  {
    synchronized (writeLock)
    {
      return searchResultsDone;
    }
  }



  /**
   * Retrieves the number of unbind requests that have been received.
   *
   * @return  The number of unbind requests that have been received.
   */
  public long getUnbindRequests()
  {
    synchronized (readLock)
    {
      return unbindRequests;
    }
  }



  /**
   * Retrieves the parent statistics tracker that will also be updated whenever
   * this tracker is updated.
   *
   * @return  The parent statistics tracker, or {@code null} if there is none.
   */
  public LDAPStatistics getParent()
  {
    return parent;
  }
}

