mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
14.23.2011 d8f732f0404da1dbcbecce85db853798c41291ce
OPENDJ-308: Implement access log filtering and configurable message format

Pull up access log filtering support into abstract base class so that it can be re-used by the audit log.


1 files added
8 files modified
2846 ■■■■ changed files
opends/resource/schema/02-config.ldif 6 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml 46 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml 45 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/AccessLogPublisherCfgDefn.properties 7 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AccessLogPublisher.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java 1365 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java 1235 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java 132 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif
@@ -2973,7 +2973,8 @@
  NAME 'ds-cfg-access-log-publisher'
  SUP ds-cfg-log-publisher
  STRUCTURAL
  MAY ( ds-cfg-suppress-internal-operations $
  MAY ( ds-cfg-filtering-policy $
        ds-cfg-suppress-internal-operations $
        ds-cfg-suppress-synchronization-operations )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.23
@@ -3008,8 +3009,7 @@
        ds-cfg-buffer-size $
        ds-cfg-auto-flush $
        ds-cfg-append $
        ds-cfg-queue-size $
        ds-cfg-filtering-policy )
        ds-cfg-queue-size )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.26
  NAME 'ds-cfg-file-based-debug-log-publisher'
opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml
@@ -24,6 +24,7 @@
  !
  !
  !      Copyright 2007-2008 Sun Microsystems, Inc.
  !      Portions copyright 2011 ForgeRock AS.
  ! -->
<adm:managed-object name="access-log-publisher"
  plural-name="access-log-publishers"
@@ -49,6 +50,15 @@
  <adm:profile name="cli">
    <cli:managed-object custom="true" />
  </adm:profile>
  <adm:relation name="access-log-filtering-criteria">
    <adm:synopsis>
      The set of criteria which will be used to filter log records.
    </adm:synopsis>
    <adm:one-to-many/>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>cn=Filtering Criteria</ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:property name="java-class" mandatory="true">
    <adm:synopsis>
      The fully-qualified name of the Java class that provides the
@@ -68,6 +78,42 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="filtering-policy">
    <adm:synopsis>
      Specifies how filtering criteria should be applied to log records.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>no-filtering</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="no-filtering">
          <adm:synopsis>
            No filtering will be performed, and all records will be logged.
          </adm:synopsis>
        </adm:value>
        <adm:value name="inclusive">
          <adm:synopsis>
            Records must match at least one of the filtering criteria in order
            to be logged.
          </adm:synopsis>
        </adm:value>
        <adm:value name="exclusive">
          <adm:synopsis>
            Records must not match any of the filtering criteria in order to be
            logged.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-filtering-policy</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="suppress-internal-operations" advanced="true">
    <adm:synopsis>
      Indicates whether internal operations (for example, operations 
opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml
@@ -41,15 +41,6 @@
      <ldap:superior>ds-cfg-access-log-publisher</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:relation name="access-log-filtering-criteria">
    <adm:synopsis>
      The set of criteria which will be used to filter log records.
    </adm:synopsis>
    <adm:one-to-many/>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>cn=Filtering Criteria</ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:property-override name="java-class" advanced="true">
    <adm:default-behavior>
      <adm:defined>
@@ -295,40 +286,4 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="filtering-policy">
    <adm:synopsis>
      Specifies how filtering criteria should be applied to log records.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>no-filtering</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="no-filtering">
          <adm:synopsis>
            No filtering will be performed, and all records will be logged.
          </adm:synopsis>
        </adm:value>
        <adm:value name="inclusive">
          <adm:synopsis>
            Records must match at least one of the filtering criteria in order
            to be logged.
          </adm:synopsis>
        </adm:value>
        <adm:value name="exclusive">
          <adm:synopsis>
            Records must not match any of the filtering criteria in order to be
            logged.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-filtering-policy</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/messages/AccessLogPublisherCfgDefn.properties
@@ -3,6 +3,13 @@
synopsis=Access Log Publishers are responsible for distributing access log messages from the access logger to a destination.
description=Access log messages provide information about the types of operations processed by the server.
property.enabled.synopsis=Indicates whether the Access Log Publisher is enabled for use.
property.filtering-policy.synopsis=Specifies how filtering criteria should be applied to log records.
property.filtering-policy.syntax.enumeration.value.exclusive.synopsis=Records must not match any of the filtering criteria in order to be logged.
property.filtering-policy.syntax.enumeration.value.inclusive.synopsis=Records must match at least one of the filtering criteria in order to be logged.
property.filtering-policy.syntax.enumeration.value.no-filtering.synopsis=No filtering will be performed, and all records will be logged.
property.java-class.synopsis=The fully-qualified name of the Java class that provides the Access Log Publisher implementation.
property.suppress-internal-operations.synopsis=Indicates whether internal operations (for example, operations that are initiated by plugins) should be logged along with the operations that are requested by users.
property.suppress-synchronization-operations.synopsis=Indicates whether access messages that are generated by synchronization operations should be suppressed.
relation.access-log-filtering-criteria.user-friendly-name=Access Log Filtering Criteria
relation.access-log-filtering-criteria.user-friendly-plural-name=Access Log Filtering Criteria
relation.access-log-filtering-criteria.synopsis=The set of criteria which will be used to filter log records.
opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties
@@ -7,6 +7,10 @@
property.auto-flush.description=If the asynchronous writes option is used, the writer is flushed after all the log records in the queue are written.
property.buffer-size.synopsis=Specifies the log file buffer size.
property.enabled.synopsis=Indicates whether the File Based Audit Log Publisher is enabled for use.
property.filtering-policy.synopsis=Specifies how filtering criteria should be applied to log records.
property.filtering-policy.syntax.enumeration.value.exclusive.synopsis=Records must not match any of the filtering criteria in order to be logged.
property.filtering-policy.syntax.enumeration.value.inclusive.synopsis=Records must match at least one of the filtering criteria in order to be logged.
property.filtering-policy.syntax.enumeration.value.no-filtering.synopsis=No filtering will be performed, and all records will be logged.
property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based Audit Log Publisher implementation.
property.log-file.synopsis=The file name to use for the log files generated by the File Based Audit Log Publisher. The path to the file is relative to the server root.
property.log-file.syntax.string.pattern.synopsis=A path to an existing file that is readable by the server.
@@ -24,3 +28,6 @@
property.suppress-internal-operations.synopsis=Indicates whether internal operations (for example, operations that are initiated by plugins) should be logged along with the operations that are requested by users.
property.suppress-synchronization-operations.synopsis=Indicates whether access messages that are generated by synchronization operations should be suppressed.
property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated.
relation.access-log-filtering-criteria.user-friendly-name=Access Log Filtering Criteria
relation.access-log-filtering-criteria.user-friendly-plural-name=Access Log Filtering Criteria
relation.access-log-filtering-criteria.synopsis=The set of criteria which will be used to filter log records.
opends/src/server/org/opends/server/api/AccessLogPublisher.java
@@ -96,8 +96,7 @@
   * @return {@code true} if the provided configuration is acceptable
   *         for this access log publisher, or {@code false} if not.
   */
  public boolean isConfigurationAcceptable(
      AccessLogPublisherCfg configuration,
  public boolean isConfigurationAcceptable(T configuration,
      List<Message> unacceptableReasons)
  {
    // This default implementation does not perform any special
opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java
New file
@@ -0,0 +1,1365 @@
/*
 * 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 2011 ForgeRock AS
 */
package org.opends.server.loggers;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.std.meta.AccessLogFilteringCriteriaCfgDefn.*;
import org.opends.server.admin.std.meta.AccessLogPublisherCfgDefn.*;
import org.opends.server.admin.std.server.AccessLogFilteringCriteriaCfg;
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
import org.opends.server.api.AccessLogPublisher;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.Group;
import org.opends.server.authorization.dseecompat.PatternDN;
import org.opends.server.config.ConfigException;
import org.opends.server.core.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
/**
 * This class provides the base implementation of the access loggers used by the
 * directory server.
 *
 * @param <T>
 *          The type of access log publisher configuration.
 */
public abstract class AbstractTextAccessLogPublisher
    <T extends AccessLogPublisherCfg> extends AccessLogPublisher<T>
{
  /**
   * Criteria based filter.
   */
  static final class CriteriaFilter implements Filter
  {
    private final AccessLogFilteringCriteriaCfg cfg;
    private final boolean logConnectRecords;
    private final boolean logDisconnectRecords;
    private final EnumSet<OperationType> logOperationRecords;
    private final AddressMask[] clientAddressEqualTo;
    private final AddressMask[] clientAddressNotEqualTo;
    private final PatternDN[] userDNEqualTo;
    private final PatternDN[] userDNNotEqualTo;
    private final PatternDN[] targetDNEqualTo;
    private final PatternDN[] targetDNNotEqualTo;
    private final DN[] userIsMemberOf;
    private final DN[] userIsNotMemberOf;
    private final String attachmentName;
    /**
     * Creates a new criteria based filter.
     *
     * @param cfg
     *          The access log filter criteria.
     * @throws DirectoryException
     *           If the configuration cannot be parsed.
     */
    CriteriaFilter(final AccessLogFilteringCriteriaCfg cfg)
        throws DirectoryException
    {
      this.cfg = cfg;
      // Generate a unique identifier for attaching partial results to
      // operations.
      attachmentName = this.getClass().getName() + "#" + hashCode();
      // Pre-parse the log record types for more efficient queries.
      if (cfg.getLogRecordType().isEmpty())
      {
        logConnectRecords = true;
        logDisconnectRecords = true;
        logOperationRecords = EnumSet.allOf(OperationType.class);
      }
      else
      {
        logConnectRecords = cfg.getLogRecordType().contains(
            LogRecordType.CONNECT);
        logDisconnectRecords = cfg.getLogRecordType().contains(
            LogRecordType.DISCONNECT);
        logOperationRecords = EnumSet.noneOf(OperationType.class);
        for (final LogRecordType type : cfg.getLogRecordType())
        {
          switch (type)
          {
          case ABANDON:
            logOperationRecords.add(OperationType.ABANDON);
            break;
          case ADD:
            logOperationRecords.add(OperationType.ADD);
            break;
          case BIND:
            logOperationRecords.add(OperationType.BIND);
            break;
          case COMPARE:
            logOperationRecords.add(OperationType.COMPARE);
            break;
          case DELETE:
            logOperationRecords.add(OperationType.DELETE);
            break;
          case EXTENDED:
            logOperationRecords.add(OperationType.EXTENDED);
            break;
          case MODIFY:
            logOperationRecords.add(OperationType.MODIFY);
            break;
          case RENAME:
            logOperationRecords.add(OperationType.MODIFY_DN);
            break;
          case SEARCH:
            logOperationRecords.add(OperationType.SEARCH);
            break;
          case UNBIND:
            logOperationRecords.add(OperationType.UNBIND);
            break;
          default: // Ignore CONNECT/DISCONNECT
            break;
          }
        }
      }
      clientAddressEqualTo = cfg.getClientAddressEqualTo().toArray(
          new AddressMask[0]);
      clientAddressNotEqualTo = cfg.getClientAddressNotEqualTo().toArray(
          new AddressMask[0]);
      userDNEqualTo = new PatternDN[cfg.getUserDNEqualTo().size()];
      int i = 0;
      for (final String s : cfg.getUserDNEqualTo())
      {
        userDNEqualTo[i++] = PatternDN.decode(s);
      }
      userDNNotEqualTo = new PatternDN[cfg.getUserDNNotEqualTo().size()];
      i = 0;
      for (final String s : cfg.getUserDNNotEqualTo())
      {
        userDNNotEqualTo[i++] = PatternDN.decode(s);
      }
      userIsMemberOf = cfg.getUserIsMemberOf().toArray(new DN[0]);
      userIsNotMemberOf = cfg.getUserIsNotMemberOf().toArray(new DN[0]);
      targetDNEqualTo = new PatternDN[cfg.getRequestTargetDNEqualTo().size()];
      i = 0;
      for (final String s : cfg.getRequestTargetDNEqualTo())
      {
        targetDNEqualTo[i++] = PatternDN.decode(s);
      }
      targetDNNotEqualTo = new PatternDN[cfg.getRequestTargetDNNotEqualTo()
          .size()];
      i = 0;
      for (final String s : cfg.getRequestTargetDNNotEqualTo())
      {
        targetDNNotEqualTo[i++] = PatternDN.decode(s);
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      if (!logConnectRecords)
      {
        return false;
      }
      if (!filterClientConnection(connection))
      {
        return false;
      }
      return true;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      if (!logDisconnectRecords)
      {
        return false;
      }
      if (!filterClientConnection(connection))
      {
        return false;
      }
      if (!filterUser(connection))
      {
        return false;
      }
      return true;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isRequestLoggable(final Operation operation)
    {
      final ClientConnection connection = operation.getClientConnection();
      final boolean matches = logOperationRecords.contains(operation
          .getOperationType())
          && filterClientConnection(connection)
          && filterUser(connection) && filterRequest(operation);
      // Cache the result so that it does not need to be recomputed for the
      // response.
      operation.setAttachment(attachmentName, matches);
      return matches;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isResponseLoggable(final Operation operation)
    {
      // First check the result that was computed for the initial request.
      Boolean requestMatched = (Boolean) operation
          .getAttachment(attachmentName);
      if (requestMatched == null)
      {
        // This should not happen.
        if (debugEnabled())
        {
          TRACER.debugWarning(
              "Operation attachment %s not found while logging response",
              attachmentName);
        }
        requestMatched = isRequestLoggable(operation);
      }
      if (!requestMatched)
      {
        return false;
      }
      // Check the response parameters.
      if (!filterResponse(operation))
      {
        return false;
      }
      return true;
    }
    private boolean filterClientConnection(final ClientConnection connection)
    {
      // Check client address.
      final InetAddress ipAddr = connection.getRemoteAddress();
      if (clientAddressNotEqualTo.length > 0)
      {
        if (AddressMask.maskListContains(ipAddr, clientAddressNotEqualTo))
        {
          return false;
        }
      }
      if (clientAddressEqualTo.length > 0)
      {
        if (!AddressMask.maskListContains(ipAddr, clientAddressEqualTo))
        {
          return false;
        }
      }
      // Check server port.
      if (!cfg.getClientPortEqualTo().isEmpty())
      {
        if (!cfg.getClientPortEqualTo().contains(connection.getServerPort()))
        {
          return false;
        }
      }
      // Check protocol.
      if (!cfg.getClientProtocolEqualTo().isEmpty())
      {
        if (!cfg.getClientProtocolEqualTo().contains(
            toLowerCase(connection.getProtocol())))
        {
          return false;
        }
      }
      return true;
    }
    private boolean filterRequest(final Operation operation)
    {
      // Check target DN.
      if (targetDNNotEqualTo.length > 0 || targetDNEqualTo.length > 0)
      {
        if (!filterRequestTargetDN(operation))
        {
          return false;
        }
      }
      // TODO: check required controls.
      return true;
    }
    private boolean filterRequestTargetDN(final Operation operation)
    {
      // Obtain both the parsed and unparsed target DNs. Requests are logged
      // before parsing so usually only the raw unparsed target DN will be
      // present, and it may even be invalid.
      DN targetDN = null;
      ByteString rawTargetDN = null;
      switch (operation.getOperationType())
      {
      case ABANDON:
      case UNBIND:
        // These operations don't have parameters which we can filter so
        // always match them.
        return true;
      case EXTENDED:
        // These operations could have parameters which can be filtered but
        // we'd need to decode the request in order to find out. This is
        // beyond the scope of the access log. Therefore, treat extended
        // operations like abandon/unbind.
        return true;
      case ADD:
        targetDN = ((AddOperation) operation).getEntryDN();
        rawTargetDN = ((AddOperation) operation).getRawEntryDN();
        break;
      case BIND:
        // For SASL bind operations the bind DN, if provided, will require the
        // SASL credentials to be decoded which is beyond the scope of the
        // access log.
        targetDN = ((BindOperation) operation).getBindDN();
        rawTargetDN = ((BindOperation) operation).getRawBindDN();
        break;
      case COMPARE:
        targetDN = ((CompareOperation) operation).getEntryDN();
        rawTargetDN = ((CompareOperation) operation).getRawEntryDN();
        break;
      case DELETE:
        targetDN = ((DeleteOperation) operation).getEntryDN();
        rawTargetDN = ((DeleteOperation) operation).getRawEntryDN();
        break;
      case MODIFY:
        targetDN = ((ModifyOperation) operation).getEntryDN();
        rawTargetDN = ((ModifyOperation) operation).getRawEntryDN();
        break;
      case MODIFY_DN:
        targetDN = ((ModifyDNOperation) operation).getEntryDN();
        rawTargetDN = ((ModifyDNOperation) operation).getRawEntryDN();
        break;
      case SEARCH:
        targetDN = ((SearchOperation) operation).getBaseDN();
        rawTargetDN = ((SearchOperation) operation).getRawBaseDN();
        break;
      }
      // Attempt to parse the raw target DN if needed.
      if (targetDN == null)
      {
        try
        {
          targetDN = DN.decode(rawTargetDN);
        }
        catch (final DirectoryException e)
        {
          // The DN raw target DN was invalid. It will never match any
          // not-equal-to nor equal-to patterns, so return appropriate result.
          if (targetDNEqualTo.length != 0)
          {
            // Invalid DN will never match equal-to patterns.
            return false;
          }
          else
          {
            // Invalid DN does not match any not-equal-to patterns.
            return true;
          }
        }
      }
      if (targetDNNotEqualTo.length > 0)
      {
        for (final PatternDN pattern : targetDNNotEqualTo)
        {
          if (pattern.matchesDN(targetDN))
          {
            return false;
          }
        }
      }
      if (targetDNEqualTo.length > 0)
      {
        for (final PatternDN pattern : targetDNNotEqualTo)
        {
          if (pattern.matchesDN(targetDN))
          {
            return true;
          }
        }
      }
      // The target DN did not match.
      return false;
    }
    private boolean filterResponse(final Operation operation)
    {
      // Check response code.
      final Integer resultCode = operation.getResultCode().getIntValue();
      if (!cfg.getResponseResultCodeNotEqualTo().isEmpty())
      {
        if (cfg.getResponseResultCodeNotEqualTo().contains(resultCode))
        {
          return false;
        }
      }
      if (!cfg.getResponseResultCodeEqualTo().isEmpty())
      {
        if (!cfg.getResponseResultCodeNotEqualTo().contains(resultCode))
        {
          return false;
        }
      }
      // Check etime.
      final long etime = operation.getProcessingTime();
      final Integer etimeGT = cfg.getResponseEtimeLessThan();
      if (etimeGT != null)
      {
        if (etime <= ((long) etimeGT))
        {
          return false;
        }
      }
      final Integer etimeLT = cfg.getResponseEtimeLessThan();
      if (etimeLT != null)
      {
        if (etime >= ((long) etimeLT))
        {
          return false;
        }
      }
      // Check search response fields.
      if (operation instanceof SearchOperation)
      {
        final SearchOperation searchOperation = (SearchOperation) operation;
        final Boolean isIndexed = cfg.isSearchResponseIsIndexed();
        if (isIndexed != null)
        {
          boolean wasUnindexed = false;
          for (final AdditionalLogItem item : operation.getAdditionalLogItems())
          {
            if (item.getKey().equals("unindexed"))
            {
              wasUnindexed = true;
              break;
            }
          }
          if (isIndexed)
          {
            if (wasUnindexed)
            {
              return false;
            }
          }
          else
          {
            if (!wasUnindexed)
            {
              return false;
            }
          }
        }
        final int nentries = searchOperation.getEntriesSent();
        final Integer nentriesGT = cfg.getSearchResponseNentriesGreaterThan();
        if (nentriesGT != null)
        {
          if (nentries <= nentriesGT)
          {
            return false;
          }
        }
        final Integer nentriesLT = cfg.getSearchResponseNentriesLessThan();
        if (nentriesLT != null)
        {
          if (nentries >= nentriesLT)
          {
            return false;
          }
        }
      }
      return true;
    }
    private boolean filterUser(final ClientConnection connection)
    {
      // Check user DN.
      if (userDNNotEqualTo.length > 0 || userDNEqualTo.length > 0)
      {
        if (!filterUserBindDN(connection))
        {
          return false;
        }
      }
      // Check group membership.
      if (userIsNotMemberOf.length > 0 || userIsNotMemberOf.length > 0)
      {
        if (!filterUserIsMemberOf(connection))
        {
          return false;
        }
      }
      return true;
    }
    private boolean filterUserBindDN(final ClientConnection connection)
    {
      final DN userDN = connection.getAuthenticationInfo()
          .getAuthenticationDN();
      // Fast-path for unauthenticated clients.
      if (userDN == null)
      {
        return userDNEqualTo.length == 0;
      }
      if (userDNNotEqualTo.length > 0)
      {
        for (final PatternDN pattern : userDNNotEqualTo)
        {
          if (pattern.matchesDN(userDN))
          {
            return false;
          }
        }
      }
      if (userDNEqualTo.length > 0)
      {
        for (final PatternDN pattern : userDNNotEqualTo)
        {
          if (pattern.matchesDN(userDN))
          {
            return true;
          }
        }
      }
      // The user DN did not match.
      return false;
    }
    private boolean filterUserIsMemberOf(final ClientConnection connection)
    {
      final Entry userEntry = connection.getAuthenticationInfo()
          .getAuthenticationEntry();
      // Fast-path for unauthenticated clients.
      if (userEntry == null)
      {
        return userIsMemberOf.length == 0;
      }
      final GroupManager groupManager = DirectoryServer.getGroupManager();
      if (userIsNotMemberOf.length > 0)
      {
        for (final DN groupDN : userIsNotMemberOf)
        {
          final Group<?> group = groupManager.getGroupInstance(groupDN);
          try
          {
            if ((group != null) && group.isMember(userEntry))
            {
              return false;
            }
          }
          catch (final DirectoryException e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
      if (userIsMemberOf.length > 0)
      {
        for (final DN groupDN : userIsMemberOf)
        {
          final Group<?> group = groupManager.getGroupInstance(groupDN);
          try
          {
            if ((group != null) && group.isMember(userEntry))
            {
              return true;
            }
          }
          catch (final DirectoryException e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
      // The user entry did not match.
      return false;
    }
  }
  /**
   * Log message filter predicate.
   */
  static interface Filter
  {
    /**
     * Returns {@code true} if the provided client connect should be logged.
     *
     * @param connection
     *          The client connection.
     * @return {@code true} if the provided client connect should be logged.
     */
    boolean isConnectLoggable(ClientConnection connection);
    /**
     * Returns {@code true} if the provided client disconnect should be logged.
     *
     * @param connection
     *          The client connection.
     * @return {@code true} if the provided client disconnect should be logged.
     */
    boolean isDisconnectLoggable(ClientConnection connection);
    /**
     * Returns {@code true} if the provided request should be logged.
     *
     * @param operation
     *          The request.
     * @return {@code true} if the provided request should be logged.
     */
    boolean isRequestLoggable(Operation operation);
    /**
     * Returns {@code true} if the provided response should be logged.
     *
     * @param operation
     *          The response.
     * @return {@code true} if the provided response should be logged.
     */
    boolean isResponseLoggable(Operation operation);
  }
  /**
   * A filter which performs a logical OR over a set of sub-filters.
   */
  static final class OrFilter implements Filter
  {
    private final Filter[] subFilters;
    /**
     * Creates a new OR filter.
     *
     * @param subFilters
     *          The sub-filters.
     */
    OrFilter(final Filter[] subFilters)
    {
      this.subFilters = subFilters;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isConnectLoggable(connection))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isDisconnectLoggable(connection))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isRequestLoggable(final Operation operation)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isRequestLoggable(operation))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isResponseLoggable(final Operation operation)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isResponseLoggable(operation))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
  }
  /**
   * The root filter which first checks the logger configuration, delegating to
   * a sub-filter if needed.
   */
  static final class RootFilter implements Filter
  {
    private final Filter subFilter;
    private final boolean suppressInternalOperations;
    private final boolean suppressSynchronizationOperations;
    private final FilteringPolicy policy;
    /**
     * Creates a new root filter.
     *
     * @param suppressInternal
     *          Indicates whether internal operations should be suppressed.
     * @param suppressSynchronization
     *          Indicates whether sync operations should be suppressed.
     * @param policy
     *          The filtering policy.
     * @param subFilter
     *          The sub-filters.
     */
    RootFilter(final boolean suppressInternal,
        final boolean suppressSynchronization, final FilteringPolicy policy,
        final Filter subFilter)
    {
      this.suppressInternalOperations = suppressInternal;
      this.suppressSynchronizationOperations = suppressSynchronization;
      this.policy = policy;
      this.subFilter = subFilter;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      final long connectionID = connection.getConnectionID();
      if (connectionID >= 0 || !suppressInternalOperations)
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isConnectLoggable(connection);
        case EXCLUSIVE:
          return !subFilter.isConnectLoggable(connection);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      final long connectionID = connection.getConnectionID();
      if (connectionID >= 0 || !suppressInternalOperations)
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isDisconnectLoggable(connection);
        case EXCLUSIVE:
          return !subFilter.isDisconnectLoggable(connection);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isRequestLoggable(final Operation operation)
    {
      if (isLoggable(operation))
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isRequestLoggable(operation);
        case EXCLUSIVE:
          return !subFilter.isRequestLoggable(operation);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isResponseLoggable(final Operation operation)
    {
      if (isLoggable(operation))
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isResponseLoggable(operation);
        case EXCLUSIVE:
          return !subFilter.isResponseLoggable(operation);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    // Determines whether the provided operation should be logged.
    private boolean isLoggable(final Operation operation)
    {
      final long connectionID = operation.getConnectionID();
      if (connectionID < 0)
      {
        // This is an internal operation.
        if (operation.isSynchronizationOperation())
        {
          return !suppressSynchronizationOperations;
        }
        else
        {
          return !suppressInternalOperations;
        }
      }
      return true;
    }
  }
  /**
   * Configuration change listener.
   */
  private final class ChangeListener implements
      ConfigurationChangeListener<AccessLogPublisherCfg>
  {
    /**
     * {@inheritDoc}
     */
    @Override
    public final ConfigChangeResult applyConfigurationChange(
        final AccessLogPublisherCfg configuration)
    {
      // Update the configuration.
      cfg = configuration;
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean isConfigurationChangeAcceptable(
        final AccessLogPublisherCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
  }
  /**
   * Filter criteria configuration listener.
   */
  private final class FilterListener implements
      ConfigurationChangeListener<AccessLogFilteringCriteriaCfg>,
      ConfigurationAddListener<AccessLogFilteringCriteriaCfg>,
      ConfigurationDeleteListener<AccessLogFilteringCriteriaCfg>
  {
    /**
     * {@inheritDoc}
     */
    @Override
    public ConfigChangeResult applyConfigurationAdd(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      configuration.addChangeListener(this);
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ConfigChangeResult applyConfigurationChange(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ConfigChangeResult applyConfigurationDelete(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConfigurationAddAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConfigurationChangeAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isConfigurationDeleteAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
  }
  /**
   * The tracer object for the debug logger.
   */
  protected static final DebugTracer TRACER = getTracer();
  private AccessLogPublisherCfg cfg = null;
  private Filter filter = null;
  private final ChangeListener changeListener = new ChangeListener();
  private final FilterListener filterListener = new FilterListener();
  /**
   * {@inheritDoc}
   */
  @Override
  public final void close()
  {
    try
    {
      close0();
    }
    finally
    {
      if (cfg != null)
      {
        cfg.removeAccessChangeListener(changeListener);
        for (final String criteriaName : cfg.listAccessLogFilteringCriteria())
        {
          try
          {
            cfg.getAccessLogFilteringCriteria(criteriaName)
                .removeChangeListener(filterListener);
          }
          catch (final ConfigException e)
          {
            // Ignore.
          }
        }
        cfg.removeAccessLogFilteringCriteriaAddListener(filterListener);
        cfg.removeAccessLogFilteringCriteriaDeleteListener(filterListener);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public final DN getDN()
  {
    return cfg != null ? cfg.dn() : null;
  }
  /**
   * Perform any initialization required by the sub-implementation.
   *
   * @param config
   *          The access publisher configuration that contains the information
   *          to use to initialize this access publisher.
   * @throws ConfigException
   *           If an unrecoverable problem arises in the process of performing
   *           the initialization as a result of the server configuration.
   * @throws InitializationException
   *           If a problem occurs during initialization that is not related to
   *           the server configuration.
   */
  protected final void initializeFilters(T config) throws ConfigException,
      InitializationException
  {
    // Now initialize filters and listeners.
    cfg = config;
    // Rebuild the filter using the new configuration and criteria.
    buildFilters();
    // Add change listeners.
    for (final String criteriaName : cfg.listAccessLogFilteringCriteria())
    {
      try
      {
        cfg.getAccessLogFilteringCriteria(criteriaName).addChangeListener(
            filterListener);
      }
      catch (final ConfigException e)
      {
        // Ignore.
      }
    }
    cfg.addAccessLogFilteringCriteriaAddListener(filterListener);
    cfg.addAccessLogFilteringCriteriaDeleteListener(filterListener);
    cfg.addAccessChangeListener(changeListener);
  }
  /**
   * Release any resources owned by the sub-implementation.
   */
  protected abstract void close0();
  /**
   * Returns {@code true} if the provided client connect should be logged.
   *
   * @param c
   *          The client connection.
   * @return {@code true} if the provided client connect should be logged.
   */
  protected final boolean isConnectLoggable(final ClientConnection c)
  {
    return filter.isConnectLoggable(c);
  }
  /**
   * Returns {@code true} if the provided client disconnect should be logged.
   *
   * @param c
   *          The client connection.
   * @return {@code true} if the provided client disconnect should be logged.
   */
  protected final boolean isDisconnectLoggable(final ClientConnection c)
  {
    return filter.isDisconnectLoggable(c);
  }
  /**
   * Returns {@code true} if the provided request should be logged.
   *
   * @param o
   *          The request.
   * @return {@code true} if the provided request should be logged.
   */
  protected final boolean isRequestLoggable(final Operation o)
  {
    return filter.isRequestLoggable(o);
  }
  /**
   * Returns {@code true} if the provided response should be logged.
   *
   * @param o
   *          The response.
   * @return {@code true} if the provided response should be logged.
   */
  protected final boolean isResponseLoggable(final Operation o)
  {
    return filter.isResponseLoggable(o);
  }
  // Build an appropriate set of filters based on the configuration.
  private void buildFilters()
  {
    buildFilters(cfg.isSuppressInternalOperations(),
        cfg.isSuppressSynchronizationOperations(), cfg.getFilteringPolicy());
  }
  /**
   * For startup access logger.
   *
   * @param suppressInternal
   *          {@code true} if internal operations should be suppressed.
   */
  protected void buildFilters(final boolean suppressInternal)
  {
    buildFilters(suppressInternal, false, FilteringPolicy.NO_FILTERING);
  }
  private void buildFilters(final boolean suppressInternal,
      final boolean suppressSynchronization, final FilteringPolicy policy)
  {
    final ArrayList<Filter> subFilters = new ArrayList<Filter>();
    if (cfg != null)
    {
      for (final String criteriaName : cfg.listAccessLogFilteringCriteria())
      {
        try
        {
          final AccessLogFilteringCriteriaCfg criteriaCfg = cfg
              .getAccessLogFilteringCriteria(criteriaName);
          subFilters.add(new CriteriaFilter(criteriaCfg));
        }
        catch (final ConfigException e)
        {
          // TODO: Unable to decode this access log criteria, so log a warning
          // and continue.
        }
        catch (final DirectoryException e)
        {
          // TODO: Unable to decode this access log criteria, so log a warning
          // and continue.
        }
      }
    }
    final Filter orFilter = new OrFilter(subFilters.toArray(new Filter[0]));
    filter = new RootFilter(suppressInternal, suppressSynchronization, policy,
        orFilter);
  }
}
opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
@@ -30,35 +30,24 @@
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.getFileForPath;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.std.meta.AccessLogFilteringCriteriaCfgDefn.*;
import org.opends.server.admin.std.meta.FileBasedAccessLogPublisherCfgDefn.*;
import org.opends.server.admin.std.server.AccessLogFilteringCriteriaCfg;
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
import org.opends.server.admin.std.server.FileBasedAccessLogPublisherCfg;
import org.opends.server.api.AccessLogPublisher;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ExtendedOperationHandler;
import org.opends.server.api.Group;
import org.opends.server.authorization.dseecompat.PatternDN;
import org.opends.server.config.ConfigException;
import org.opends.server.core.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.util.TimeThread;
@@ -68,1041 +57,10 @@
 * This class provides the implementation of the access logger used by the
 * directory server.
 */
public class TextAccessLogPublisher extends
    AccessLogPublisher<FileBasedAccessLogPublisherCfg> implements
public final class TextAccessLogPublisher extends
    AbstractTextAccessLogPublisher<FileBasedAccessLogPublisherCfg> implements
    ConfigurationChangeListener<FileBasedAccessLogPublisherCfg>
{
  /**
   * Criteria based filter.
   */
  static final class CriteriaFilter implements Filter
  {
    private final AccessLogFilteringCriteriaCfg cfg;
    private final boolean logConnectRecords;
    private final boolean logDisconnectRecords;
    private final EnumSet<OperationType> logOperationRecords;
    private final AddressMask[] clientAddressEqualTo;
    private final AddressMask[] clientAddressNotEqualTo;
    private final PatternDN[] userDNEqualTo;
    private final PatternDN[] userDNNotEqualTo;
    private final PatternDN[] targetDNEqualTo;
    private final PatternDN[] targetDNNotEqualTo;
    private final DN[] userIsMemberOf;
    private final DN[] userIsNotMemberOf;
    private final String attachmentName;
    /**
     * Creates a new criteria based filter.
     *
     * @param cfg
     *          The access log filter criteria.
     * @throws DirectoryException
     *           If the configuration cannot be parsed.
     */
    CriteriaFilter(final AccessLogFilteringCriteriaCfg cfg)
        throws DirectoryException
    {
      this.cfg = cfg;
      // Generate a unique identifier for attaching partial results to
      // operations.
      attachmentName = this.getClass().getName() + "#" + hashCode();
      // Pre-parse the log record types for more efficient queries.
      if (cfg.getLogRecordType().isEmpty())
      {
        logConnectRecords = true;
        logDisconnectRecords = true;
        logOperationRecords = EnumSet.allOf(OperationType.class);
      }
      else
      {
        logConnectRecords = cfg.getLogRecordType().contains(
            LogRecordType.CONNECT);
        logDisconnectRecords = cfg.getLogRecordType().contains(
            LogRecordType.DISCONNECT);
        logOperationRecords = EnumSet.noneOf(OperationType.class);
        for (final LogRecordType type : cfg.getLogRecordType())
        {
          switch (type)
          {
          case ABANDON:
            logOperationRecords.add(OperationType.ABANDON);
            break;
          case ADD:
            logOperationRecords.add(OperationType.ADD);
            break;
          case BIND:
            logOperationRecords.add(OperationType.BIND);
            break;
          case COMPARE:
            logOperationRecords.add(OperationType.COMPARE);
            break;
          case DELETE:
            logOperationRecords.add(OperationType.DELETE);
            break;
          case EXTENDED:
            logOperationRecords.add(OperationType.EXTENDED);
            break;
          case MODIFY:
            logOperationRecords.add(OperationType.MODIFY);
            break;
          case RENAME:
            logOperationRecords.add(OperationType.MODIFY_DN);
            break;
          case SEARCH:
            logOperationRecords.add(OperationType.SEARCH);
            break;
          case UNBIND:
            logOperationRecords.add(OperationType.UNBIND);
            break;
          default: // Ignore CONNECT/DISCONNECT
            break;
          }
        }
      }
      clientAddressEqualTo = cfg.getClientAddressEqualTo().toArray(
          new AddressMask[0]);
      clientAddressNotEqualTo = cfg.getClientAddressNotEqualTo().toArray(
          new AddressMask[0]);
      userDNEqualTo = new PatternDN[cfg.getUserDNEqualTo().size()];
      int i = 0;
      for (final String s : cfg.getUserDNEqualTo())
      {
        userDNEqualTo[i++] = PatternDN.decode(s);
      }
      userDNNotEqualTo = new PatternDN[cfg.getUserDNNotEqualTo().size()];
      i = 0;
      for (final String s : cfg.getUserDNNotEqualTo())
      {
        userDNNotEqualTo[i++] = PatternDN.decode(s);
      }
      userIsMemberOf = cfg.getUserIsMemberOf().toArray(new DN[0]);
      userIsNotMemberOf = cfg.getUserIsNotMemberOf().toArray(new DN[0]);
      targetDNEqualTo = new PatternDN[cfg.getRequestTargetDNEqualTo().size()];
      i = 0;
      for (final String s : cfg.getRequestTargetDNEqualTo())
      {
        targetDNEqualTo[i++] = PatternDN.decode(s);
      }
      targetDNNotEqualTo = new PatternDN[cfg.getRequestTargetDNNotEqualTo()
          .size()];
      i = 0;
      for (final String s : cfg.getRequestTargetDNNotEqualTo())
      {
        targetDNNotEqualTo[i++] = PatternDN.decode(s);
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      if (!logConnectRecords)
      {
        return false;
      }
      if (!filterClientConnection(connection))
      {
        return false;
      }
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      if (!logDisconnectRecords)
      {
        return false;
      }
      if (!filterClientConnection(connection))
      {
        return false;
      }
      if (!filterUser(connection))
      {
        return false;
      }
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(final Operation operation)
    {
      final ClientConnection connection = operation.getClientConnection();
      final boolean matches = logOperationRecords.contains(operation
          .getOperationType())
          && filterClientConnection(connection)
          && filterUser(connection) && filterRequest(operation);
      // Cache the result so that it does not need to be recomputed for the
      // response.
      operation.setAttachment(attachmentName, matches);
      return matches;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(final Operation operation)
    {
      // First check the result that was computed for the initial request.
      Boolean requestMatched = (Boolean) operation
          .getAttachment(attachmentName);
      if (requestMatched == null)
      {
        // This should not happen.
        if (debugEnabled())
        {
          TRACER.debugWarning(
              "Operation attachment %s not found while logging response",
              attachmentName);
        }
        requestMatched = isRequestLoggable(operation);
      }
      if (!requestMatched)
      {
        return false;
      }
      // Check the response parameters.
      if (!filterResponse(operation))
      {
        return false;
      }
      return true;
    }
    private boolean filterClientConnection(final ClientConnection connection)
    {
      // Check client address.
      final InetAddress ipAddr = connection.getRemoteAddress();
      if (clientAddressNotEqualTo.length > 0)
      {
        if (AddressMask.maskListContains(ipAddr, clientAddressNotEqualTo))
        {
          return false;
        }
      }
      if (clientAddressEqualTo.length > 0)
      {
        if (!AddressMask.maskListContains(ipAddr, clientAddressEqualTo))
        {
          return false;
        }
      }
      // Check server port.
      if (!cfg.getClientPortEqualTo().isEmpty())
      {
        if (!cfg.getClientPortEqualTo().contains(connection.getServerPort()))
        {
          return false;
        }
      }
      // Check protocol.
      if (!cfg.getClientProtocolEqualTo().isEmpty())
      {
        if (!cfg.getClientProtocolEqualTo().contains(
            toLowerCase(connection.getProtocol())))
        {
          return false;
        }
      }
      return true;
    }
    private boolean filterRequest(final Operation operation)
    {
      // Check target DN.
      if (targetDNNotEqualTo.length > 0 || targetDNEqualTo.length > 0)
      {
        if (!filterRequestTargetDN(operation))
        {
          return false;
        }
      }
      // TODO: check required controls.
      return true;
    }
    private boolean filterRequestTargetDN(final Operation operation)
    {
      // Obtain both the parsed and unparsed target DNs. Requests are logged
      // before parsing so usually only the raw unparsed target DN will be
      // present, and it may even be invalid.
      DN targetDN = null;
      ByteString rawTargetDN = null;
      switch (operation.getOperationType())
      {
      case ABANDON:
      case UNBIND:
        // These operations don't have parameters which we can filter so
        // always match them.
        return true;
      case EXTENDED:
        // These operations could have parameters which can be filtered but
        // we'd need to decode the request in order to find out. This is
        // beyond the scope of the access log. Therefore, treat extended
        // operations like abandon/unbind.
        return true;
      case ADD:
        targetDN = ((AddOperation) operation).getEntryDN();
        rawTargetDN = ((AddOperation) operation).getRawEntryDN();
        break;
      case BIND:
        // For SASL bind operations the bind DN, if provided, will require the
        // SASL credentials to be decoded which is beyond the scope of the
        // access log.
        targetDN = ((BindOperation) operation).getBindDN();
        rawTargetDN = ((BindOperation) operation).getRawBindDN();
        break;
      case COMPARE:
        targetDN = ((CompareOperation) operation).getEntryDN();
        rawTargetDN = ((CompareOperation) operation).getRawEntryDN();
        break;
      case DELETE:
        targetDN = ((DeleteOperation) operation).getEntryDN();
        rawTargetDN = ((DeleteOperation) operation).getRawEntryDN();
        break;
      case MODIFY:
        targetDN = ((ModifyOperation) operation).getEntryDN();
        rawTargetDN = ((ModifyOperation) operation).getRawEntryDN();
        break;
      case MODIFY_DN:
        targetDN = ((ModifyDNOperation) operation).getEntryDN();
        rawTargetDN = ((ModifyDNOperation) operation).getRawEntryDN();
        break;
      case SEARCH:
        targetDN = ((SearchOperation) operation).getBaseDN();
        rawTargetDN = ((SearchOperation) operation).getRawBaseDN();
        break;
      }
      // Attempt to parse the raw target DN if needed.
      if (targetDN == null)
      {
        try
        {
          targetDN = DN.decode(rawTargetDN);
        }
        catch (final DirectoryException e)
        {
          // The DN raw target DN was invalid. It will never match any
          // not-equal-to nor equal-to patterns, so return appropriate result.
          if (targetDNEqualTo.length != 0)
          {
            // Invalid DN will never match equal-to patterns.
            return false;
          }
          else
          {
            // Invalid DN does not match any not-equal-to patterns.
            return true;
          }
        }
      }
      if (targetDNNotEqualTo.length > 0)
      {
        for (final PatternDN pattern : targetDNNotEqualTo)
        {
          if (pattern.matchesDN(targetDN))
          {
            return false;
          }
        }
      }
      if (targetDNEqualTo.length > 0)
      {
        for (final PatternDN pattern : targetDNNotEqualTo)
        {
          if (pattern.matchesDN(targetDN))
          {
            return true;
          }
        }
      }
      // The target DN did not match.
      return false;
    }
    private boolean filterResponse(final Operation operation)
    {
      // Check response code.
      final Integer resultCode = operation.getResultCode().getIntValue();
      if (!cfg.getResponseResultCodeNotEqualTo().isEmpty())
      {
        if (cfg.getResponseResultCodeNotEqualTo().contains(resultCode))
        {
          return false;
        }
      }
      if (!cfg.getResponseResultCodeEqualTo().isEmpty())
      {
        if (!cfg.getResponseResultCodeNotEqualTo().contains(resultCode))
        {
          return false;
        }
      }
      // Check etime.
      final long etime = operation.getProcessingTime();
      final Integer etimeGT = cfg.getResponseEtimeLessThan();
      if (etimeGT != null)
      {
        if (etime <= ((long) etimeGT))
        {
          return false;
        }
      }
      final Integer etimeLT = cfg.getResponseEtimeLessThan();
      if (etimeLT != null)
      {
        if (etime >= ((long) etimeLT))
        {
          return false;
        }
      }
      // Check search response fields.
      if (operation instanceof SearchOperation)
      {
        final SearchOperation searchOperation = (SearchOperation) operation;
        final Boolean isIndexed= cfg.isSearchResponseIsIndexed();
        if (isIndexed != null)
        {
          boolean wasUnindexed = false;
          for (final AdditionalLogItem item : operation.getAdditionalLogItems())
          {
            if (item.getKey().equals("unindexed"))
            {
              wasUnindexed = true;
              break;
            }
          }
          if (isIndexed)
          {
            if (wasUnindexed)
            {
              return false;
            }
          }
          else
          {
            if (!wasUnindexed)
            {
              return false;
            }
          }
        }
        final int nentries = searchOperation.getEntriesSent();
        final Integer nentriesGT = cfg.getSearchResponseNentriesGreaterThan();
        if (nentriesGT != null)
        {
          if (nentries <= nentriesGT)
          {
            return false;
          }
        }
        final Integer nentriesLT = cfg.getSearchResponseNentriesLessThan();
        if (nentriesLT != null)
        {
          if (nentries >= nentriesLT)
          {
            return false;
          }
        }
      }
      return true;
    }
    private boolean filterUser(final ClientConnection connection)
    {
      // Check user DN.
      if (userDNNotEqualTo.length > 0 || userDNEqualTo.length > 0)
      {
        if (!filterUserBindDN(connection))
        {
          return false;
        }
      }
      // Check group membership.
      if (userIsNotMemberOf.length > 0 || userIsNotMemberOf.length > 0)
      {
        if (!filterUserIsMemberOf(connection))
        {
          return false;
        }
      }
      return true;
    }
    private boolean filterUserBindDN(final ClientConnection connection)
    {
      final DN userDN = connection.getAuthenticationInfo()
          .getAuthenticationDN();
      // Fast-path for unauthenticated clients.
      if (userDN == null)
      {
        return userDNEqualTo.length == 0;
      }
      if (userDNNotEqualTo.length > 0)
      {
        for (final PatternDN pattern : userDNNotEqualTo)
        {
          if (pattern.matchesDN(userDN))
          {
            return false;
          }
        }
      }
      if (userDNEqualTo.length > 0)
      {
        for (final PatternDN pattern : userDNNotEqualTo)
        {
          if (pattern.matchesDN(userDN))
          {
            return true;
          }
        }
      }
      // The user DN did not match.
      return false;
    }
    private boolean filterUserIsMemberOf(final ClientConnection connection)
    {
      final Entry userEntry = connection.getAuthenticationInfo()
          .getAuthenticationEntry();
      // Fast-path for unauthenticated clients.
      if (userEntry == null)
      {
        return userIsMemberOf.length == 0;
      }
      final GroupManager groupManager = DirectoryServer.getGroupManager();
      if (userIsNotMemberOf.length > 0)
      {
        for (final DN groupDN : userIsNotMemberOf)
        {
          final Group<?> group = groupManager.getGroupInstance(groupDN);
          try
          {
            if ((group != null) && group.isMember(userEntry))
            {
              return false;
            }
          }
          catch (final DirectoryException e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
      if (userIsMemberOf.length > 0)
      {
        for (final DN groupDN : userIsMemberOf)
        {
          final Group<?> group = groupManager.getGroupInstance(groupDN);
          try
          {
            if ((group != null) && group.isMember(userEntry))
            {
              return true;
            }
          }
          catch (final DirectoryException e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
      // The user entry did not match.
      return false;
    }
  }
  // TODO: update assigned OIDs WIKI page when complete.
  /**
   * Log message filter predicate.
   */
  static interface Filter
  {
    /**
     * Returns {@code true} if the provided client connect should be logged.
     *
     * @param connection
     *          The client connection.
     * @return {@code true} if the provided client connect should be logged.
     */
    boolean isConnectLoggable(ClientConnection connection);
    /**
     * Returns {@code true} if the provided client disconnect should be logged.
     *
     * @param connection
     *          The client connection.
     * @return {@code true} if the provided client disconnect should be logged.
     */
    boolean isDisconnectLoggable(ClientConnection connection);
    /**
     * Returns {@code true} if the provided request should be logged.
     *
     * @param operation
     *          The request.
     * @return {@code true} if the provided request should be logged.
     */
    boolean isRequestLoggable(Operation operation);
    /**
     * Returns {@code true} if the provided response should be logged.
     *
     * @param operation
     *          The response.
     * @return {@code true} if the provided response should be logged.
     */
    boolean isResponseLoggable(Operation operation);
  }
  /**
   * A filter which performs a logical OR over a set of sub-filters.
   */
  static final class OrFilter implements Filter
  {
    private final Filter[] subFilters;
    /**
     * Creates a new OR filter.
     *
     * @param subFilters
     *          The sub-filters.
     */
    OrFilter(final Filter[] subFilters)
    {
      this.subFilters = subFilters;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isConnectLoggable(connection))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isDisconnectLoggable(connection))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(final Operation operation)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isRequestLoggable(operation))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(final Operation operation)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isResponseLoggable(operation))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
  }
  /**
   * The root filter which first checks the logger configuration, delegating to
   * a sub-filter if needed.
   */
  static final class RootFilter implements Filter
  {
    private final Filter subFilter;
    private final boolean suppressInternalOperations;
    private final boolean suppressSynchronizationOperations;
    private final FilteringPolicy policy;
    /**
     * Creates a new root filter.
     *
     * @param suppressInternal
     *          Indicates whether internal operations should be suppressed.
     * @param suppressSynchronization
     *          Indicates whether sync operations should be suppressed.
     * @param policy
     *          The filtering policy.
     * @param subFilter
     *          The sub-filters.
     */
    RootFilter(final boolean suppressInternal,
        final boolean suppressSynchronization, final FilteringPolicy policy,
        final Filter subFilter)
    {
      this.suppressInternalOperations = suppressInternal;
      this.suppressSynchronizationOperations = suppressSynchronization;
      this.policy = policy;
      this.subFilter = subFilter;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      final long connectionID = connection.getConnectionID();
      if (connectionID >= 0 || !suppressInternalOperations)
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isConnectLoggable(connection);
        case EXCLUSIVE:
          return !subFilter.isConnectLoggable(connection);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      final long connectionID = connection.getConnectionID();
      if (connectionID >= 0 || !suppressInternalOperations)
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isDisconnectLoggable(connection);
        case EXCLUSIVE:
          return !subFilter.isDisconnectLoggable(connection);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(final Operation operation)
    {
      if (isLoggable(operation))
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isRequestLoggable(operation);
        case EXCLUSIVE:
          return !subFilter.isRequestLoggable(operation);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(final Operation operation)
    {
      if (isLoggable(operation))
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isResponseLoggable(operation);
        case EXCLUSIVE:
          return !subFilter.isResponseLoggable(operation);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    // Determines whether the provided operation should be logged.
    private boolean isLoggable(final Operation operation)
    {
      final long connectionID = operation.getConnectionID();
      if (connectionID < 0)
      {
        // This is an internal operation.
        if (operation.isSynchronizationOperation())
        {
          return !suppressSynchronizationOperations;
        }
        else
        {
          return !suppressInternalOperations;
        }
      }
      return true;
    }
  }
  /**
   * Filter criteria configuration listener.
   */
  private final class FilterChangeListener implements
      ConfigurationChangeListener<AccessLogFilteringCriteriaCfg>,
      ConfigurationAddListener<AccessLogFilteringCriteriaCfg>,
      ConfigurationDeleteListener<AccessLogFilteringCriteriaCfg>
  {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationAdd(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      configuration.addChangeListener(this);
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationChange(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationDelete(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationAddAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationChangeAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationDeleteAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
  }
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The category to use when logging responses.
@@ -1134,17 +92,14 @@
    final TextAccessLogPublisher startupPublisher =
      new TextAccessLogPublisher();
    startupPublisher.writer = writer;
    startupPublisher.buildFilters(suppressInternal, false,
        FilteringPolicy.NO_FILTERING);
    startupPublisher.buildFilters(suppressInternal);
    return startupPublisher;
  }
  private FileBasedAccessLogPublisherCfg currentConfig = null;
  private TextWriter writer = null;
  private Filter filter = null;
  private FilterChangeListener filterChangeListener = null;
  private FileBasedAccessLogPublisherCfg cfg = null;
@@ -1246,16 +201,13 @@
          writer = asyncWriter;
        }
        if ((currentConfig.isAsynchronous() && config.isAsynchronous())
            && (currentConfig.getQueueSize() != config.getQueueSize()))
        if ((cfg.isAsynchronous() && config.isAsynchronous())
            && (cfg.getQueueSize() != config.getQueueSize()))
        {
          adminActionRequired = true;
        }
        currentConfig = config;
        // Rebuild the filter using the new configuration and criteria.
        buildFilters();
        cfg = config;
      }
    }
    catch (final Exception e)
@@ -1276,49 +228,12 @@
   * {@inheritDoc}
   */
  @Override
  public void close()
  protected void close0()
  {
    writer.shutdown();
    if (currentConfig != null)
    if (cfg != null)
    {
      currentConfig.removeFileBasedAccessChangeListener(this);
      for (final String criteriaName : currentConfig
          .listAccessLogFilteringCriteria())
      {
        try
        {
          currentConfig.getAccessLogFilteringCriteria(criteriaName)
              .removeChangeListener(filterChangeListener);
        }
        catch (final ConfigException e)
        {
          // Ignore.
        }
      }
      currentConfig
          .removeAccessLogFilteringCriteriaAddListener(filterChangeListener);
      currentConfig
          .removeAccessLogFilteringCriteriaDeleteListener(filterChangeListener);
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public DN getDN()
  {
    if (currentConfig != null)
    {
      return currentConfig.dn();
    }
    else
    {
      return null;
      cfg.removeFileBasedAccessChangeListener(this);
    }
  }
@@ -1386,7 +301,6 @@
      final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg
          .dn().toString(), String.valueOf(e));
      throw new InitializationException(message, e);
    }
    catch (final IOException e)
    {
@@ -1396,32 +310,9 @@
    }
    currentConfig = cfg;
    // Rebuild the filter using the new configuration and criteria.
    buildFilters();
    // Add change listeners.
    filterChangeListener = new FilterChangeListener();
    for (final String criteriaName : currentConfig
        .listAccessLogFilteringCriteria())
    {
      try
      {
        currentConfig.getAccessLogFilteringCriteria(criteriaName)
            .addChangeListener(filterChangeListener);
      }
      catch (final ConfigException e)
      {
        // Ignore.
      }
    }
    currentConfig
        .addAccessLogFilteringCriteriaAddListener(filterChangeListener);
    currentConfig
        .addAccessLogFilteringCriteriaDeleteListener(filterChangeListener);
    currentConfig.addFileBasedAccessChangeListener(this);
    initializeFilters(cfg);
    this.cfg = cfg;
    cfg.addFileBasedAccessChangeListener(this);
  }
@@ -1431,12 +322,10 @@
   */
  @Override
  public boolean isConfigurationAcceptable(
      final AccessLogPublisherCfg configuration,
      final FileBasedAccessLogPublisherCfg configuration,
      final List<Message> unacceptableReasons)
  {
    final FileBasedAccessLogPublisherCfg config =
      (FileBasedAccessLogPublisherCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
    return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
  }
@@ -1485,7 +374,7 @@
  @Override
  public void logAbandonRequest(final AbandonOperation abandonOperation)
  {
    if (!filter.isRequestLoggable(abandonOperation))
    if (!isRequestLoggable(abandonOperation))
    {
      return;
    }
@@ -1515,7 +404,7 @@
  @Override
  public void logAbandonResult(final AbandonOperation abandonOperation)
  {
    if (!filter.isResponseLoggable(abandonOperation))
    if (!isResponseLoggable(abandonOperation))
    {
      return;
    }
@@ -1553,7 +442,7 @@
  @Override
  public void logAddRequest(final AddOperation addOperation)
  {
    if (!filter.isRequestLoggable(addOperation))
    if (!isRequestLoggable(addOperation))
    {
      return;
    }
@@ -1584,7 +473,7 @@
  @Override
  public void logAddResponse(final AddOperation addOperation)
  {
    if (!filter.isResponseLoggable(addOperation))
    if (!isResponseLoggable(addOperation))
    {
      return;
    }
@@ -1636,7 +525,7 @@
  @Override
  public void logBindRequest(final BindOperation bindOperation)
  {
    if (!filter.isRequestLoggable(bindOperation))
    if (!isRequestLoggable(bindOperation))
    {
      return;
    }
@@ -1690,7 +579,7 @@
  @Override
  public void logBindResponse(final BindOperation bindOperation)
  {
    if (!filter.isResponseLoggable(bindOperation))
    if (!isResponseLoggable(bindOperation))
    {
      return;
    }
@@ -1774,7 +663,7 @@
  @Override
  public void logCompareRequest(final CompareOperation compareOperation)
  {
    if (!filter.isRequestLoggable(compareOperation))
    if (!isRequestLoggable(compareOperation))
    {
      return;
    }
@@ -1806,7 +695,7 @@
  @Override
  public void logCompareResponse(final CompareOperation compareOperation)
  {
    if (!filter.isResponseLoggable(compareOperation))
    if (!isResponseLoggable(compareOperation))
    {
      return;
    }
@@ -1858,7 +747,7 @@
  @Override
  public void logConnect(final ClientConnection clientConnection)
  {
    if (!filter.isConnectLoggable(clientConnection))
    if (!isConnectLoggable(clientConnection))
    {
      return;
    }
@@ -1894,7 +783,7 @@
  @Override
  public void logDeleteRequest(final DeleteOperation deleteOperation)
  {
    if (!filter.isRequestLoggable(deleteOperation))
    if (!isRequestLoggable(deleteOperation))
    {
      return;
    }
@@ -1925,7 +814,7 @@
  @Override
  public void logDeleteResponse(final DeleteOperation deleteOperation)
  {
    if (!filter.isResponseLoggable(deleteOperation))
    if (!isResponseLoggable(deleteOperation))
    {
      return;
    }
@@ -1982,7 +871,7 @@
  public void logDisconnect(final ClientConnection clientConnection,
      final DisconnectReason disconnectReason, final Message message)
  {
    if (!filter.isDisconnectLoggable(clientConnection))
    if (!isDisconnectLoggable(clientConnection))
    {
      return;
    }
@@ -2021,7 +910,7 @@
  @Override
  public void logExtendedRequest(final ExtendedOperation extendedOperation)
  {
    if (!filter.isRequestLoggable(extendedOperation))
    if (!isRequestLoggable(extendedOperation))
    {
      return;
    }
@@ -2066,7 +955,7 @@
  @Override
  public void logExtendedResponse(final ExtendedOperation extendedOperation)
  {
    if (!filter.isResponseLoggable(extendedOperation))
    if (!isResponseLoggable(extendedOperation))
    {
      return;
    }
@@ -2132,7 +1021,7 @@
  @Override
  public void logModifyDNRequest(final ModifyDNOperation modifyDNOperation)
  {
    if (!filter.isRequestLoggable(modifyDNOperation))
    if (!isRequestLoggable(modifyDNOperation))
    {
      return;
    }
@@ -2173,7 +1062,7 @@
  @Override
  public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation)
  {
    if (!filter.isResponseLoggable(modifyDNOperation))
    if (!isResponseLoggable(modifyDNOperation))
    {
      return;
    }
@@ -2225,7 +1114,7 @@
  @Override
  public void logModifyRequest(final ModifyOperation modifyOperation)
  {
    if (!filter.isRequestLoggable(modifyOperation))
    if (!isRequestLoggable(modifyOperation))
    {
      return;
    }
@@ -2256,7 +1145,7 @@
  @Override
  public void logModifyResponse(final ModifyOperation modifyOperation)
  {
    if (!filter.isResponseLoggable(modifyOperation))
    if (!isResponseLoggable(modifyOperation))
    {
      return;
    }
@@ -2308,7 +1197,7 @@
  @Override
  public void logSearchRequest(final SearchOperation searchOperation)
  {
    if (!filter.isRequestLoggable(searchOperation))
    if (!isRequestLoggable(searchOperation))
    {
      return;
    }
@@ -2362,7 +1251,7 @@
  @Override
  public void logSearchResultDone(final SearchOperation searchOperation)
  {
    if (!filter.isResponseLoggable(searchOperation))
    if (!isResponseLoggable(searchOperation))
    {
      return;
    }
@@ -2418,7 +1307,7 @@
  public void logUnbind(final UnbindOperation unbindOperation)
  {
    // FIXME: ensure that these are logged in combined mode.
    if (!filter.isRequestLoggable(unbindOperation))
    if (!isRequestLoggable(unbindOperation))
    {
      return;
    }
@@ -2455,50 +1344,6 @@
  // Build an appropriate set of filters based on the configuration.
  private void buildFilters()
  {
    buildFilters(currentConfig.isSuppressInternalOperations(),
        currentConfig.isSuppressSynchronizationOperations(),
        currentConfig.getFilteringPolicy());
  }
  private void buildFilters(final boolean suppressInternal,
      final boolean suppressSynchronization, final FilteringPolicy policy)
  {
    final ArrayList<Filter> subFilters = new ArrayList<Filter>();
    if (currentConfig != null)
    {
      for (final String criteriaName : currentConfig
          .listAccessLogFilteringCriteria())
      {
        try
        {
          final AccessLogFilteringCriteriaCfg cfg = currentConfig
              .getAccessLogFilteringCriteria(criteriaName);
          subFilters.add(new CriteriaFilter(cfg));
        }
        catch (final ConfigException e)
        {
          // TODO: Unable to decode this access log criteria, so log a warning
          // and continue.
        }
        catch (final DirectoryException e)
        {
          // TODO: Unable to decode this access log criteria, so log a warning
          // and continue.
        }
      }
    }
    final Filter orFilter = new OrFilter(subFilters.toArray(new Filter[0]));
    filter = new RootFilter(suppressInternal, suppressSynchronization, policy,
        orFilter);
  }
  // Appends additional log items to the provided builder.
  private void logAdditionalLogItems(final Operation operation,
      final StringBuilder builder)
opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
@@ -30,9 +30,11 @@
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.types.ResultCode.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.types.ResultCode.SUCCESS;
import static org.opends.server.util.ServerConstants.EOL;
import static org.opends.server.util.StaticUtils.getBytes;
import static org.opends.server.util.StaticUtils.getFileForPath;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.io.File;
import java.io.IOException;
@@ -41,15 +43,9 @@
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
import org.opends.server.admin.std.server.FileBasedAuditLogPublisherCfg;
import org.opends.server.api.AccessLogPublisher;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.util.Base64;
import org.opends.server.util.StaticUtils;
@@ -61,18 +57,14 @@
 * This class provides the implementation of the audit logger used by
 * the directory server.
 */
public class TextAuditLogPublisher extends
    AccessLogPublisher<FileBasedAuditLogPublisherCfg> implements
public final class TextAuditLogPublisher extends
    AbstractTextAccessLogPublisher<FileBasedAuditLogPublisherCfg> implements
    ConfigurationChangeListener<FileBasedAuditLogPublisherCfg>
{
  private boolean suppressInternalOperations = true;
  private boolean suppressSynchronizationOperations = false;
  private TextWriter writer;
  private FileBasedAuditLogPublisherCfg currentConfig;
  private FileBasedAuditLogPublisherCfg cfg;
@@ -87,10 +79,6 @@
    boolean adminActionRequired = false;
    ArrayList<Message> messages = new ArrayList<Message>();
    suppressInternalOperations = config.isSuppressInternalOperations();
    suppressSynchronizationOperations = config
        .isSuppressSynchronizationOperations();
    File logFile = getFileForPath(config.getLogFile());
    FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
@@ -157,13 +145,13 @@
          writer = asyncWriter;
        }
        if ((currentConfig.isAsynchronous() && config.isAsynchronous())
            && (currentConfig.getQueueSize() != config.getQueueSize()))
        if ((cfg.isAsynchronous() && config.isAsynchronous())
            && (cfg.getQueueSize() != config.getQueueSize()))
        {
          adminActionRequired = true;
        }
        currentConfig = config;
        cfg = config;
      }
    }
    catch (Exception e)
@@ -184,28 +172,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void close()
  protected void close0()
  {
    writer.shutdown();
    currentConfig.removeFileBasedAuditChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public DN getDN()
  {
    if (currentConfig != null)
    {
      return currentConfig.dn();
    }
    else
    {
      return null;
    }
    cfg.removeFileBasedAuditChangeListener(this);
  }
@@ -214,44 +184,43 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializeAccessLogPublisher(
      FileBasedAuditLogPublisherCfg config)
  public void initializeAccessLogPublisher(FileBasedAuditLogPublisherCfg cfg)
      throws ConfigException, InitializationException
  {
    File logFile = getFileForPath(config.getLogFile());
    File logFile = getFileForPath(cfg.getLogFile());
    FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
    try
    {
      FilePermission perm = FilePermission.decodeUNIXMode(config
      FilePermission perm = FilePermission.decodeUNIXMode(cfg
          .getLogFilePermissions());
      LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler(
          config.dn());
          cfg.dn());
      boolean writerAutoFlush = config.isAutoFlush()
          && !config.isAsynchronous();
      boolean writerAutoFlush = cfg.isAutoFlush()
          && !cfg.isAsynchronous();
      MultifileTextWriter writer = new MultifileTextWriter(
          "Multifile Text Writer for " + config.dn().toNormalizedString(),
          config.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8",
          writerAutoFlush, config.isAppend(), (int) config.getBufferSize());
          "Multifile Text Writer for " + cfg.dn().toNormalizedString(),
          cfg.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8",
          writerAutoFlush, cfg.isAppend(), (int) cfg.getBufferSize());
      // Validate retention and rotation policies.
      for (DN dn : config.getRotationPolicyDNs())
      for (DN dn : cfg.getRotationPolicyDNs())
      {
        writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
      }
      for (DN dn : config.getRetentionPolicyDNs())
      for (DN dn : cfg.getRetentionPolicyDNs())
      {
        writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
      if (config.isAsynchronous())
      if (cfg.isAsynchronous())
      {
        this.writer = new AsyncronousTextWriter("Asyncronous Text Writer for "
            + config.dn().toNormalizedString(), config.getQueueSize(), config
            + cfg.dn().toNormalizedString(), cfg.getQueueSize(), cfg
            .isAutoFlush(), writer);
      }
      else
@@ -261,7 +230,7 @@
    }
    catch (DirectoryException e)
    {
      Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn()
      Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg.dn()
          .toString(), String.valueOf(e));
      throw new InitializationException(message, e);
@@ -269,18 +238,14 @@
    catch (IOException e)
    {
      Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile
          .toString(), config.dn().toString(), String.valueOf(e));
          .toString(), cfg.dn().toString(), String.valueOf(e));
      throw new InitializationException(message, e);
    }
    suppressInternalOperations = config.isSuppressInternalOperations();
    suppressSynchronizationOperations = config
        .isSuppressSynchronizationOperations();
    currentConfig = config;
    config.addFileBasedAuditChangeListener(this);
    initializeFilters(cfg);
    this.cfg = cfg;
    cfg.addFileBasedAuditChangeListener(this);
  }
@@ -289,12 +254,11 @@
   * {@inheritDoc}
   */
  @Override
  public boolean isConfigurationAcceptable(AccessLogPublisherCfg configuration,
  public boolean isConfigurationAcceptable(
      FileBasedAuditLogPublisherCfg configuration,
      List<Message> unacceptableReasons)
  {
    FileBasedAuditLogPublisherCfg config =
      (FileBasedAuditLogPublisherCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
    return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
  }
@@ -605,31 +569,13 @@
  // Determines whether the provided operation should be logged.
  private boolean isLoggable(Operation operation)
  {
    long connectionID = operation.getConnectionID();
    if (connectionID < 0)
    {
      // This is an internal operation.
      if (operation.isSynchronizationOperation())
      {
        if (suppressSynchronizationOperations)
        {
          return false;
        }
      }
      else
      {
        if (suppressInternalOperations)
        {
          return false;
        }
      }
    }
    if (operation.getResultCode() != SUCCESS)
    {
      return false;
    }
    return true;
    else
    {
      return isResponseLoggable(operation);
    }
  }
}