From 8750a1a2fa4c91aec7c95ca65c901b859e4378bd Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 13 Oct 2011 22:23:00 +0000
Subject: [PATCH] OPENDJ-308: Implement access log filtering and configurable message format

---
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml          |   46 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml |   45 
 opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java                         |  132 -
 opendj-sdk/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java                        | 1235 ------------------------
 opendj-sdk/opends/src/server/org/opends/server/api/AccessLogPublisher.java                                |    3 
 opendj-sdk/opends/src/admin/messages/AccessLogPublisherCfgDefn.properties                                 |    7 
 opendj-sdk/opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties                         |    7 
 opendj-sdk/opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java                | 1365 ++++++++++++++++++++++++++++
 opendj-sdk/opends/resource/schema/02-config.ldif                                                          |    6 
 9 files changed, 1,508 insertions(+), 1,338 deletions(-)

diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index a64fb8f..3f7a575 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/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'
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml
index c797d2b..2ff68ea 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml
+++ b/opendj-sdk/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 
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml
index 3249731..9934bc6 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml
+++ b/opendj-sdk/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>
diff --git a/opendj-sdk/opends/src/admin/messages/AccessLogPublisherCfgDefn.properties b/opendj-sdk/opends/src/admin/messages/AccessLogPublisherCfgDefn.properties
index 14822ef..d302cc1 100644
--- a/opendj-sdk/opends/src/admin/messages/AccessLogPublisherCfgDefn.properties
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties b/opendj-sdk/opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties
index c684c21..8712b77 100644
--- a/opendj-sdk/opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/AccessLogPublisher.java b/opendj-sdk/opends/src/server/org/opends/server/api/AccessLogPublisher.java
index 4468ff9..df2de94 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/AccessLogPublisher.java
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java b/opendj-sdk/opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java
new file mode 100644
index 0000000..07f528d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java
@@ -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);
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java b/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
index 346e4f5..6b039ce 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
+++ b/opendj-sdk/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)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java b/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
index 2e639f7..7683d89 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
+++ b/opendj-sdk/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);
+    }
   }
 }

--
Gitblit v1.10.0