From 834a8ae734c7af26826962d52e1aace2bd7bbef3 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 03 May 2013 12:33:08 +0000
Subject: [PATCH] OPENDJ-879 (CR-1642) Add HTTP access log

---
 opends/resource/schema/02-config.ldif                                                              |   22 
 opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml          |   59 ++
 opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java                       |   17 
 opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java                                |  101 ++++
 opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java                                   |  177 +++++++
 opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml |  316 ++++++++++++
 opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java             |   96 ++-
 opends/resource/config/config.ldif                                                                 |   15 
 opends/src/server/org/opends/server/core/LoggerConfigManager.java                                  |   26 +
 opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java                     |   38 
 opends/src/messages/messages/config.properties                                                     |    9 
 opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties                        |   26 +
 opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java                                  |  148 +++++
 opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java                        |  409 ++++++++++++++++
 opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties                                 |    6 
 15 files changed, 1,410 insertions(+), 55 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index e5221c9..8c659e0 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -727,6 +727,21 @@
 ds-cfg-rotation-policy: cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config
 ds-cfg-retention-policy: cn=File Count Retention Policy,cn=Log Retention Policies,cn=config
 
+dn: cn=File-Based HTTP Access Logger,cn=Loggers,cn=config
+objectClass: top
+objectClass: ds-cfg-log-publisher
+objectClass: ds-cfg-http-access-log-publisher
+objectClass: ds-cfg-file-based-http-access-log-publisher
+cn: File-Based HTTP Access Logger
+ds-cfg-java-class: org.opends.server.loggers.TextHTTPAccessLogPublisher
+ds-cfg-enabled: true
+ds-cfg-log-file: logs/http-access
+ds-cfg-log-file-permissions: 640
+ds-cfg-asynchronous: true
+ds-cfg-rotation-policy: cn=24 Hours Time Limit Rotation Policy,cn=Log Rotation Policies,cn=config
+ds-cfg-rotation-policy: cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config
+ds-cfg-retention-policy: cn=File Count Retention Policy,cn=Log Retention Policies,cn=config
+
 dn: cn=File-Based Audit Logger,cn=Loggers,cn=config
 objectClass: top
 objectClass: ds-cfg-log-publisher
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index d620358..e456f2d 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -3937,6 +3937,11 @@
         ds-cfg-suppress-internal-operations $
         ds-cfg-suppress-synchronization-operations )
   X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.16
+  NAME 'ds-cfg-http-access-log-publisher'
+  SUP ds-cfg-log-publisher
+  STRUCTURAL
+  X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.23
   NAME 'ds-cfg-error-log-publisher'
   SUP ds-cfg-log-publisher
@@ -3974,6 +3979,23 @@
         ds-cfg-log-record-time-format $
         ds-cfg-log-control-oids )
   X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.17
+  NAME 'ds-cfg-file-based-http-access-log-publisher'
+  SUP ds-cfg-http-access-log-publisher
+  STRUCTURAL
+  MUST ( ds-cfg-log-file $
+         ds-cfg-asynchronous $
+         ds-cfg-log-file-permissions )
+  MAY ( ds-cfg-rotation-policy $
+        ds-cfg-rotation-action $
+        ds-cfg-retention-policy $
+        ds-cfg-time-interval $
+        ds-cfg-buffer-size $
+        ds-cfg-auto-flush $
+        ds-cfg-append $
+        ds-cfg-queue-size $
+        ds-cfg-log-record-time-format )
+  X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.26
   NAME 'ds-cfg-file-based-debug-log-publisher'
   SUP ds-cfg-debug-log-publisher
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml
new file mode 100644
index 0000000..dec8d36
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ! 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 2013 ForgeRock AS
+  ! -->
+<adm:managed-object name="file-based-http-access-log-publisher"
+  plural-name="file-based-http-access-log-publishers"
+  package="org.opends.server.admin.std" extends="http-access-log-publisher"
+  xmlns:adm="http://www.opends.org/admin"
+  xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    <adm:user-friendly-plural-name />
+    publish HTTP access messages to the file system.
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>ds-cfg-file-based-http-access-log-publisher</ldap:name>
+      <ldap:superior>ds-cfg-http-access-log-publisher</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.loggers.TextHTTPAccessLogPublisher
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property name="asynchronous" mandatory="true" advanced="true">
+    <adm:synopsis>
+      Indicates whether the
+      <adm:user-friendly-name />
+      will publish records asynchronously.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>true</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-asynchronous</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="queue-size" advanced="true">
+    <adm:synopsis>
+      The maximum number of log records that can be stored in the
+      asynchronous queue.
+    </adm:synopsis>
+    <adm:description>
+      Setting the queue size to zero activates parallel log writer
+      implementation which has no queue size limit and as such the
+      parallel log writer should only be used on a very well tuned
+      server configuration to avoid potential out of memory errors.
+    </adm:description>
+    <adm:requires-admin-action>
+      <adm:other>
+        <adm:synopsis>
+          The <adm:user-friendly-name /> must be restarted if this property 
+          is changed and the asynchronous property is set to true.
+        </adm:synopsis>
+      </adm:other>
+    </adm:requires-admin-action>
+        <adm:default-behavior>
+      <adm:defined>
+        <adm:value>5000</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:integer lower-limit="0" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-queue-size</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="log-file" mandatory="true">
+    <adm:synopsis>
+      The file name to use for the log files generated by the
+      <adm:user-friendly-name />.
+      The path to the file is relative to the server root.
+    </adm:synopsis>
+    <adm:requires-admin-action>
+      <adm:component-restart />
+    </adm:requires-admin-action>
+   <adm:syntax>
+     <adm:string>
+       <adm:pattern>
+        <adm:regex>.*</adm:regex>
+        <adm:usage>FILE</adm:usage>
+          <adm:synopsis>
+            A path to an existing file that is readable by the server.
+          </adm:synopsis>
+        </adm:pattern>
+      </adm:string>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-log-file</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="log-file-permissions" mandatory="true">
+    <adm:synopsis>
+      The UNIX permissions of the log files created by this
+      <adm:user-friendly-name />.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>640</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string>
+        <adm:pattern>
+          <adm:regex>^([0-7][0-7][0-7])$</adm:regex>
+          <adm:usage>MODE</adm:usage>
+          <adm:synopsis>
+            A valid UNIX mode string. The mode string must contain
+            three digits between zero and seven.
+          </adm:synopsis>
+        </adm:pattern>
+      </adm:string>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-log-file-permissions</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="time-interval" advanced="true">
+    <adm:synopsis>
+      Specifies the interval at which to check whether the log files 
+      need to be rotated.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>5s</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:duration base-unit="ms" lower-limit="1" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-time-interval</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="buffer-size" advanced="true">
+    <adm:synopsis>Specifies the log file buffer size.</adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>64kb</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:size lower-limit="1" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-buffer-size</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="auto-flush" advanced="true">
+    <adm:synopsis>
+      Specifies whether to flush the writer after every log record.
+    </adm:synopsis>
+    <adm:description>
+      If the asynchronous writes option is used, the writer is
+      flushed after all the log records in the queue are written.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>true</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-auto-flush</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="append">
+    <adm:synopsis>
+      Specifies whether to append to existing log files.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>true</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-append</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="rotation-policy" multi-valued="true">
+    <adm:synopsis>
+      The rotation policy to use for the
+      <adm:user-friendly-name />
+      .
+    </adm:synopsis>
+    <adm:description>
+      When multiple policies are used, rotation will occur if any
+      policy's conditions are met.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          No rotation policy is used and log rotation will not occur.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:aggregation parent-path="/"
+        relation-name="log-rotation-policy" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-rotation-policy</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="retention-policy" multi-valued="true">
+    <adm:synopsis>
+      The retention policy to use for the
+      <adm:user-friendly-name />
+      .
+    </adm:synopsis>
+    <adm:description>
+      When multiple policies are used, log files are cleaned when
+      any of the policy's conditions are met.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:alias>
+        <adm:synopsis>
+          No retention policy is used and log files are never cleaned.
+        </adm:synopsis>
+      </adm:alias>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:aggregation parent-path="/"
+        relation-name="log-retention-policy" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-retention-policy</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="log-record-time-format">
+    <adm:synopsis>
+      Specifies the format string that is used to generate log record
+      timestamps.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>dd/MMM/yyyy:HH:mm:ss Z</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+   <adm:syntax>
+     <adm:string>
+       <adm:pattern>
+        <adm:regex>.*</adm:regex>
+        <adm:usage>STRING</adm:usage>
+          <adm:synopsis>
+            Any valid format string that can be used with the 
+            java.text.SimpleDateFormat class.
+          </adm:synopsis>
+        </adm:pattern>
+      </adm:string>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-log-record-time-format</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+</adm:managed-object>
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml
new file mode 100644
index 0000000..c33e8e0
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ! 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 2013 ForgeRock AS
+  ! -->
+<adm:managed-object name="http-access-log-publisher"
+  plural-name="http-access-log-publishers"
+  package="org.opends.server.admin.std" extends="log-publisher"
+  xmlns:adm="http://www.opends.org/admin"
+  xmlns:ldap="http://www.opends.org/admin-ldap"
+  xmlns:cli="http://www.opends.org/admin-cli">
+  <adm:synopsis>
+    <adm:user-friendly-plural-name />
+    are responsible for distributing HTTP access log messages from the HTTP
+    access logger to a destination.
+  </adm:synopsis>
+  <adm:description>
+    HTTP access log messages provide information about the types of HTTP
+    requests processed by the server.
+  </adm:description>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>ds-cfg-http-access-log-publisher</ldap:name>
+      <ldap:superior>ds-cfg-log-publisher</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:profile name="cli">
+    <cli:managed-object custom="true" />
+  </adm:profile>
+  <adm:property-override name="java-class">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>org.opends.server.api.HTTPAccessLogPublisher</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+</adm:managed-object>
diff --git a/opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties b/opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties
new file mode 100644
index 0000000..e3f7417
--- /dev/null
+++ b/opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties
@@ -0,0 +1,26 @@
+user-friendly-name=File Based HTTP Access Log Publisher
+user-friendly-plural-name=File Based HTTP Access Log Publishers
+synopsis=File Based HTTP Access Log Publishers publish HTTP access messages to the file system.
+property.append.synopsis=Specifies whether to append to existing log files.
+property.asynchronous.synopsis=Indicates whether the File Based HTTP Access Log Publisher will publish records asynchronously.
+property.auto-flush.synopsis=Specifies whether to flush the writer after every log record.
+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 HTTP Access Log Publisher is enabled for use.
+property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based HTTP Access Log Publisher implementation.
+property.log-file.synopsis=The file name to use for the log files generated by the File Based HTTP Access 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.
+property.log-file-permissions.synopsis=The UNIX permissions of the log files created by this File Based HTTP Access Log Publisher.
+property.log-file-permissions.syntax.string.pattern.synopsis=A valid UNIX mode string. The mode string must contain three digits between zero and seven.
+property.log-record-time-format.synopsis=Specifies the format string that is used to generate log record timestamps.
+property.log-record-time-format.syntax.string.pattern.synopsis=Any valid format string that can be used with the java.text.SimpleDateFormat class.
+property.queue-size.synopsis=The maximum number of log records that can be stored in the asynchronous queue.
+property.queue-size.description=Setting the queue size to zero activates parallel log writer implementation which has no queue size limit and as such the parallel log writer should only be used on a very well tuned server configuration to avoid potential out of memory errors.
+property.queue-size.requires-admin-action.synopsis=The File Based HTTP Access Log Publisher must be restarted if this property is changed and the asynchronous property is set to true.
+property.retention-policy.synopsis=The retention policy to use for the File Based HTTP Access Log Publisher .
+property.retention-policy.description=When multiple policies are used, log files are cleaned when any of the policy's conditions are met.
+property.retention-policy.default-behavior.alias.synopsis=No retention policy is used and log files are never cleaned.
+property.rotation-policy.synopsis=The rotation policy to use for the File Based HTTP Access Log Publisher .
+property.rotation-policy.description=When multiple policies are used, rotation will occur if any policy's conditions are met.
+property.rotation-policy.default-behavior.alias.synopsis=No rotation policy is used and log rotation will not occur.
+property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated.
diff --git a/opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties b/opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties
new file mode 100644
index 0000000..d74905f
--- /dev/null
+++ b/opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties
@@ -0,0 +1,6 @@
+user-friendly-name=HTTP Access Log Publisher
+user-friendly-plural-name=HTTP Access Log Publishers
+synopsis=HTTP Access Log Publishers are responsible for distributing HTTP access log messages from the HTTP access logger to a destination.
+description=HTTP access log messages provide information about the types of HTTP requests processed by the server.
+property.enabled.synopsis=Indicates whether the HTTP Access Log Publisher is enabled for use.
+property.java-class.synopsis=The fully-qualified name of the Java class that provides the HTTP Access Log Publisher implementation.
diff --git a/opends/src/messages/messages/config.properties b/opends/src/messages/messages/config.properties
index 6b3a1f5..8ad3d2e 100644
--- a/opends/src/messages/messages/config.properties
+++ b/opends/src/messages/messages/config.properties
@@ -165,9 +165,6 @@
 SEVERE_WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS_45=There are no active \
  error loggers defined in the Directory Server configuration.  No error \
  logging will be performed
-MILD_WARN_CONFIG_LOGGER_NO_ACTIVE_DEBUG_LOGGERS_46=There are no active debug \
- loggers defined in the Directory Server configuration.  No debug logging will \
- be performed
 SEVERE_ERR_CONFIG_LOGGER_ENTRY_UNACCEPTABLE_47=Configuration entry %s does \
  not contain a valid logger configuration:  %s.  It will be ignored
 INFO_CONFIG_UNKNOWN_UNACCEPTABLE_REASON_48=Unknown unacceptable reason
@@ -2178,3 +2175,9 @@
 SEVERE_ERR_CONFIG_LOGGING_INVALID_TARGET_DN_PATTERN_730=Thee access log filtering \
  criteria defined in "%s" could not be parsed because it contains an invalid \
  target DN pattern "%s"
+SEVERE_WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS_731=There are no active \
+ HTTP access loggers defined in the Directory Server configuration.  No HTTP \
+ access logging will be performed
+SEVERE_ERR_CONFIG_LOGGER_INVALID_HTTP_ACCESS_LOGGER_CLASS_732=Class %s specified \
+ in attribute ds-cfg-java-class of configuration entry %s cannot be \
+ instantiated as a Directory Server HTTP access logger:  %s
diff --git a/opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java b/opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java
new file mode 100644
index 0000000..7f3796a
--- /dev/null
+++ b/opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java
@@ -0,0 +1,101 @@
+/*
+ * 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 2013 ForgeRock AS.
+ */
+package org.opends.server.api;
+
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg;
+import org.opends.server.loggers.HTTPRequestInfo;
+
+/**
+ * This class defines the set of methods and structures that must be implemented
+ * for a Directory Server HTTP access log publisher.
+ *
+ * @param <T>
+ *          The type of HTTP access log publisher configuration handled by this
+ *          log publisher implementation.
+ */
+@org.opends.server.types.PublicAPI(
+    stability = org.opends.server.types.StabilityLevel.VOLATILE,
+    mayInstantiate = false,
+    mayExtend = true,
+    mayInvoke = false)
+public abstract class HTTPAccessLogPublisher
+    <T extends HTTPAccessLogPublisherCfg> implements LogPublisher<T>
+{
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isConfigurationAcceptable(T configuration,
+      List<Message> unacceptableReasons)
+  {
+    // This default implementation does not perform any special
+    // validation. It should be overridden by HTTP access log publisher
+    // implementations that wish to perform more detailed validation.
+    return true;
+  }
+
+  /**
+   * Logs the request info according to the common logfile format. The common
+   * logfile format is as follows:
+   *
+   * <pre>
+   * remotehost rfc931 authuser [date] "request" status bytes "useragent"
+   * </pre>
+   * <dl>
+   * <dt>remotehost</dt>
+   * <dd>Remote hostname (or IP number if DNS hostname is not available, or if
+   * DNSLookup is Off.</dd>
+   * <dt>rfc931</dt>
+   * <dd>The remote logname of the user.</dd>
+   * <dt>authuser</dt>
+   * <dd>The username as which the user has authenticated himself.</dd>
+   * <dt>[date]</dt>
+   * <dd>Date and time of the request.</dd>
+   * <dt>"request"</dt>
+   * <dd>The request line exactly as it came from the client.</dd>
+   * <dt>status</dt>
+   * <dd>The HTTP status code returned to the client.</dd>
+   * <dt>bytes</dt>
+   * <dd>The content-length of the document transferred.</dd>
+   * <dt>"useragent"</dt>
+   * <dd>The user agent that issued the request.</dd>
+   * </dl>
+   * <p>
+   * <b>NOTE:</b> The bytes field is not currently supported.
+   * </p>
+   *
+   * @param requestInfo
+   *          The request info to log
+   */
+  public void logRequestInfo(HTTPRequestInfo requestInfo)
+  {
+    // Do nothing
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/core/LoggerConfigManager.java b/opends/src/server/org/opends/server/core/LoggerConfigManager.java
index 1ef20fd..b8eb7a9 100644
--- a/opends/src/server/org/opends/server/core/LoggerConfigManager.java
+++ b/opends/src/server/org/opends/server/core/LoggerConfigManager.java
@@ -37,11 +37,17 @@
 import org.opends.server.admin.server.ConfigurationAddListener;
 import org.opends.server.admin.server.ConfigurationDeleteListener;
 import org.opends.server.admin.server.ServerManagementContext;
-import org.opends.server.admin.std.server.*;
+import org.opends.server.admin.std.server.AccessLogPublisherCfg;
+import org.opends.server.admin.std.server.DebugLogPublisherCfg;
+import org.opends.server.admin.std.server.ErrorLogPublisherCfg;
+import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg;
+import org.opends.server.admin.std.server.LogPublisherCfg;
+import org.opends.server.admin.std.server.RootCfg;
 import org.opends.server.config.ConfigException;
 import org.opends.server.loggers.AbstractLogger;
 import org.opends.server.loggers.AccessLogger;
 import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.loggers.HTTPAccessLogger;
 import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.InitializationException;
@@ -87,6 +93,9 @@
     List<AccessLogPublisherCfg> accessPublisherCfgs =
         new ArrayList<AccessLogPublisherCfg>();
 
+    List<HTTPAccessLogPublisherCfg> httpAccessPublisherCfgs =
+        new ArrayList<HTTPAccessLogPublisherCfg>();
+
     List<ErrorLogPublisherCfg> errorPublisherCfgs =
         new ArrayList<ErrorLogPublisherCfg>();
 
@@ -102,6 +111,10 @@
       {
         accessPublisherCfgs.add((AccessLogPublisherCfg)config);
       }
+      else if (config instanceof HTTPAccessLogPublisherCfg)
+      {
+        httpAccessPublisherCfgs.add((HTTPAccessLogPublisherCfg) config);
+      }
       else if(config instanceof ErrorLogPublisherCfg)
       {
         errorPublisherCfgs.add((ErrorLogPublisherCfg)config);
@@ -116,10 +129,16 @@
 
     // See if there are active loggers in all categories.  If not, then log a
     // message.
+    // Do not output warn message for debug loggers because it is valid to fully
+    // disable all debug loggers.
     if (accessPublisherCfgs.isEmpty())
     {
       logError(WARN_CONFIG_LOGGER_NO_ACTIVE_ACCESS_LOGGERS.get());
     }
+    if (httpAccessPublisherCfgs.isEmpty())
+    {
+      logError(WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS.get());
+    }
     if (errorPublisherCfgs.isEmpty())
     {
       logError(WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS.get());
@@ -127,6 +146,7 @@
 
     DebugLogger.getInstance().initializeLogger(debugPublisherCfgs);
     AccessLogger.getInstance().initializeLogger(accessPublisherCfgs);
+    HTTPAccessLogger.getInstance().initializeLogger(httpAccessPublisherCfgs);
     ErrorLogger.getInstance().initializeLogger(errorPublisherCfgs);
   }
 
@@ -154,6 +174,10 @@
     {
       return AccessLogger.getInstance();
     }
+    else if (config instanceof HTTPAccessLogPublisherCfg)
+    {
+      return HTTPAccessLogger.getInstance();
+    }
     else if (config instanceof ErrorLogPublisherCfg)
     {
       return ErrorLogger.getInstance();
diff --git a/opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java b/opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java
new file mode 100644
index 0000000..58f80ec
--- /dev/null
+++ b/opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java
@@ -0,0 +1,148 @@
+/*
+ * 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 2013 ForgeRock AS
+ */
+package org.opends.server.loggers;
+
+import static org.opends.messages.ConfigMessages.*;
+
+import java.util.Collection;
+
+import org.opends.server.admin.ClassPropertyDefinition;
+import org.opends.server.admin.std.meta.HTTPAccessLogPublisherCfgDefn;
+import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg;
+import org.opends.server.api.HTTPAccessLogPublisher;
+
+/**
+ * This class defines the wrapper that will invoke all registered HTTP access
+ * loggers for each type of request received or response sent.
+ */
+public class HTTPAccessLogger extends AbstractLogger
+<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg>
+{
+
+  private static LoggerStorage
+  <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg>
+  loggerStorage = new LoggerStorage
+  <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg>
+  ();
+
+  /** The singleton instance of this class for configuration purposes. */
+  private static final HTTPAccessLogger instance = new HTTPAccessLogger();
+
+  /**
+   * The constructor for this class.
+   */
+  public HTTPAccessLogger()
+  {
+    super((Class) HTTPAccessLogPublisher.class,
+        ERR_CONFIG_LOGGER_INVALID_HTTP_ACCESS_LOGGER_CLASS);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected ClassPropertyDefinition getJavaClassPropertyDefinition()
+  {
+    return HTTPAccessLogPublisherCfgDefn.getInstance()
+        .getJavaClassPropertyDefinition();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected LoggerStorage<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>,
+      HTTPAccessLogPublisherCfg> getStorage()
+  {
+    return loggerStorage;
+  }
+
+  /**
+   * Retrieve the singleton instance of this class.
+   *
+   * @return The singleton instance of this logger.
+   */
+  public static HTTPAccessLogger getInstance()
+  {
+    return instance;
+  }
+
+  /**
+   * Add an HTTP access log publisher to the HTTP access logger.
+   *
+   * @param publisher
+   *          The HTTP access log publisher to add.
+   */
+  public synchronized static void addHTTPAccessLogPublisher(
+      HTTPAccessLogPublisher publisher)
+  {
+    loggerStorage.addLogPublisher(publisher);
+  }
+
+  /**
+   * Remove an HTTP access log publisher from the HTTP access logger.
+   *
+   * @param publisher
+   *          The HTTP access log publisher to remove.
+   * @return The publisher that was removed or null if it was not found.
+   */
+  public synchronized static boolean removeHTTPAccessLogPublisher(
+      HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg> publisher)
+  {
+    return loggerStorage.removeLogPublisher(publisher);
+  }
+
+  /**
+   * Removes all existing HTTP access log publishers from the logger.
+   */
+  public synchronized static void removeAllHTTPAccessLogPublishers()
+  {
+    loggerStorage.removeAllLogPublishers();
+  }
+
+  /**
+   * Returns all the registered HTTP access log publishers.
+   *
+   * @return a Collection of {@link HTTPAccessLogPublisher} objects
+   */
+  private static Collection<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>>
+      getHTTPAccessLogPublishers()
+  {
+    return loggerStorage.getLogPublishers();
+  }
+
+  /**
+   * Logs the given HTTPRequestInfo.
+   *
+   * @param requestInfo
+   *          the HTTP request info to log
+   */
+  public static void logRequestInfo(HTTPRequestInfo requestInfo)
+  {
+    for (HTTPAccessLogPublisher<?> publisher : loggerStorage.getLogPublishers())
+    {
+      publisher.logRequestInfo(requestInfo);
+    }
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java b/opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java
new file mode 100644
index 0000000..8accb23
--- /dev/null
+++ b/opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java
@@ -0,0 +1,177 @@
+/*
+ * 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 2013 ForgeRock AS
+ */
+package org.opends.server.loggers;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Contains the information required for logging the HTTP request.
+ */
+public class HTTPRequestInfo
+{
+
+  /** The client's host. */
+  private final String remoteHost;
+  /** The client's address. */
+  private final String remoteAddress;
+  /** The protocol used for this request. */
+  private final String protocol;
+  /** The HTTP method/verb used for this request. */
+  private final String method;
+  /** The query issued by the client. */
+  private final String query;
+  /** The user agent used by the client. */
+  private final String userAgent;
+
+  /** The username that was used to authenticate. */
+  private String authUser;
+  /** The HTTP status code returned to the client. */
+  private volatile Integer statusCode;
+
+  /**
+   * Constructor for this class.
+   *
+   * @param request
+   *          The {@link HttpServletRequest} for which to log the information
+   */
+  public HTTPRequestInfo(HttpServletRequest request)
+  {
+    this.remoteHost = request.getRemoteHost();
+    this.remoteAddress = request.getRemoteAddr();
+    this.method = request.getMethod();
+    this.query = request.getRequestURI() + "/" + request.getQueryString();
+    this.protocol = request.getProtocol();
+    this.userAgent = request.getHeader("User-Agent");
+  }
+
+  /**
+   * Returns the client's host.
+   *
+   * @return the remoteHost
+   */
+  public String getRemoteHost()
+  {
+    return remoteHost;
+  }
+
+  /**
+   * Returns the client's address.
+   *
+   * @return the remoteAddress
+   */
+  public String getRemoteAddress()
+  {
+    return remoteAddress;
+  }
+
+  /**
+   * Returns the protocol used for this request.
+   *
+   * @return the protocol
+   */
+  public String getProtocol()
+  {
+    return protocol;
+  }
+
+  /**
+   * Returns the HTTP method/verb used for this request.
+   *
+   * @return the method
+   */
+  public String getMethod()
+  {
+    return method;
+  }
+
+  /**
+   * Returns the query issued by the client.
+   *
+   * @return the query
+   */
+  public String getQuery()
+  {
+    return query;
+  }
+
+  /**
+   * Returns the user agent used by the client.
+   *
+   * @return the userAgent
+   */
+  public String getUserAgent()
+  {
+    return userAgent;
+  }
+
+  /**
+   * Returns the username that was used to authenticate.
+   *
+   * @return the authUser
+   */
+  public String getAuthUser()
+  {
+    return authUser;
+  }
+
+  /**
+   * Sets the username that was used to authenticate.
+   *
+   * @param authUser
+   *          the authUser to set
+   */
+  public void setAuthUser(String authUser)
+  {
+    this.authUser = authUser;
+  }
+
+  /**
+   * Returns the HTTP status code returned to the client.
+   *
+   * @return the statusCode
+   */
+  public int getStatusCode()
+  {
+    return statusCode != null ? statusCode : 200;
+  }
+
+  /**
+   * Logs the current request info in the HTTP access log.
+   *
+   * @param statusCode
+   *          the HTTP status code that was returned to the client.
+   */
+  public void log(int statusCode)
+  {
+    if (this.statusCode == null)
+    { // this request was not logged before
+      this.statusCode = statusCode;
+      HTTPAccessLogger.logRequestInfo(this);
+    }
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java b/opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java
new file mode 100644
index 0000000..efc7b3f
--- /dev/null
+++ b/opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java
@@ -0,0 +1,409 @@
+/*
+ * 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 2013 ForgeRock AS
+ */
+package org.opends.server.loggers;
+
+import static org.opends.messages.ConfigMessages.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.server.FileBasedHTTPAccessLogPublisherCfg;
+import org.opends.server.api.HTTPAccessLogPublisher;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.FilePermission;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
+
+/**
+ * This class provides the implementation of the HTTP access logger used by the
+ * directory server.
+ */
+public final class TextHTTPAccessLogPublisher extends
+    HTTPAccessLogPublisher<FileBasedHTTPAccessLogPublisherCfg>
+    implements ConfigurationChangeListener<FileBasedHTTPAccessLogPublisherCfg>
+{
+
+  /**
+   * Returns an instance of the text HTTP access log publisher that will print
+   * all messages to the provided writer. This is used to print the messages to
+   * the console when the server starts up.
+   *
+   * @param writer
+   *          The text writer where the message will be written to.
+   * @return The instance of the text error log publisher that will print all
+   *         messages to standard out.
+   */
+  public static TextHTTPAccessLogPublisher getStartupTextHTTPAccessPublisher(
+      final TextWriter writer)
+  {
+    final TextHTTPAccessLogPublisher startupPublisher =
+      new TextHTTPAccessLogPublisher();
+    startupPublisher.writer = writer;
+    return startupPublisher;
+  }
+
+
+
+  private TextWriter writer = null;
+  private FileBasedHTTPAccessLogPublisherCfg cfg = null;
+  private String timeStampFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+  private DateFormat dateFormatter = new SimpleDateFormat(timeStampFormat);
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public ConfigChangeResult applyConfigurationChange(
+      final FileBasedHTTPAccessLogPublisherCfg config)
+  {
+    // Default result code.
+    ResultCode resultCode = ResultCode.SUCCESS;
+    boolean adminActionRequired = false;
+    final ArrayList<Message> messages = new ArrayList<Message>();
+
+    final File logFile = getFileForPath(config.getLogFile());
+    final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
+    try
+    {
+      final FilePermission perm = FilePermission.decodeUNIXMode(config
+          .getLogFilePermissions());
+
+      final boolean writerAutoFlush = config.isAutoFlush()
+          && !config.isAsynchronous();
+
+      TextWriter currentWriter;
+      // Determine the writer we are using. If we were writing
+      // asynchronously, we need to modify the underlying writer.
+      if (writer instanceof AsyncronousTextWriter)
+      {
+        currentWriter = ((AsyncronousTextWriter) writer).getWrappedWriter();
+      }
+      else if (writer instanceof ParallelTextWriter)
+      {
+        currentWriter = ((ParallelTextWriter) writer).getWrappedWriter();
+      }
+      else
+      {
+        currentWriter = writer;
+      }
+
+      if (currentWriter instanceof MultifileTextWriter)
+      {
+        final MultifileTextWriter mfWriter =
+          (MultifileTextWriter) currentWriter;
+
+        mfWriter.setNamingPolicy(fnPolicy);
+        mfWriter.setFilePermissions(perm);
+        mfWriter.setAppend(config.isAppend());
+        mfWriter.setAutoFlush(writerAutoFlush);
+        mfWriter.setBufferSize((int) config.getBufferSize());
+        mfWriter.setInterval(config.getTimeInterval());
+
+        mfWriter.removeAllRetentionPolicies();
+        mfWriter.removeAllRotationPolicies();
+
+        for (final DN dn : config.getRotationPolicyDNs())
+        {
+          mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
+        }
+
+        for (final DN dn : config.getRetentionPolicyDNs())
+        {
+          mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
+        }
+
+        if (writer instanceof AsyncronousTextWriter && !config.isAsynchronous())
+        {
+          // The asynchronous setting is being turned off.
+          final AsyncronousTextWriter asyncWriter =
+            ((AsyncronousTextWriter) writer);
+          writer = mfWriter;
+          asyncWriter.shutdown(false);
+        }
+
+        if (writer instanceof ParallelTextWriter && !config.isAsynchronous())
+        {
+          // The asynchronous setting is being turned off.
+          final ParallelTextWriter asyncWriter = ((ParallelTextWriter) writer);
+          writer = mfWriter;
+          asyncWriter.shutdown(false);
+        }
+
+        if (!(writer instanceof AsyncronousTextWriter)
+            && config.isAsynchronous())
+        {
+          // The asynchronous setting is being turned on.
+          final AsyncronousTextWriter asyncWriter = new AsyncronousTextWriter(
+              "Asyncronous Text Writer for " + config.dn().toNormalizedString(),
+              config.getQueueSize(), config.isAutoFlush(), mfWriter);
+          writer = asyncWriter;
+        }
+
+        if (!(writer instanceof ParallelTextWriter) && config.isAsynchronous())
+        {
+          // The asynchronous setting is being turned on.
+          final ParallelTextWriter asyncWriter = new ParallelTextWriter(
+              "Parallel Text Writer for " + config.dn().toNormalizedString(),
+              config.isAutoFlush(), mfWriter);
+          writer = asyncWriter;
+        }
+
+        if ((cfg.isAsynchronous() && config.isAsynchronous())
+            && (cfg.getQueueSize() != config.getQueueSize()))
+        {
+          adminActionRequired = true;
+        }
+
+        if (!config.getLogRecordTimeFormat().equals(timeStampFormat))
+        {
+          TimeThread.removeUserDefinedFormatter(timeStampFormat);
+          setTimeStampFormat(config.getLogRecordTimeFormat());
+        }
+
+        cfg = config;
+      }
+    }
+    catch (final Exception e)
+    {
+      final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
+          config.dn().toString(), stackTraceToSingleLineString(e));
+      resultCode = DirectoryServer.getServerErrorResultCode();
+      messages.add(message);
+    }
+
+    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public void initializeLogPublisher(
+      final FileBasedHTTPAccessLogPublisherCfg cfg)
+      throws ConfigException, InitializationException
+  {
+    final File logFile = getFileForPath(cfg.getLogFile());
+    final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
+
+    try
+    {
+      final FilePermission perm = FilePermission.decodeUNIXMode(cfg
+          .getLogFilePermissions());
+
+      final LogPublisherErrorHandler errorHandler =
+        new LogPublisherErrorHandler(cfg.dn());
+
+      final boolean writerAutoFlush = cfg.isAutoFlush()
+          && !cfg.isAsynchronous();
+
+      final MultifileTextWriter theWriter = new MultifileTextWriter(
+          "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 (final DN dn : cfg.getRotationPolicyDNs())
+      {
+        theWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
+      }
+
+      for (final DN dn : cfg.getRetentionPolicyDNs())
+      {
+        theWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
+      }
+
+      if (cfg.isAsynchronous())
+      {
+        if (cfg.getQueueSize() > 0)
+        {
+          this.writer = new AsyncronousTextWriter(
+              "Asyncronous Text Writer for " + cfg.dn().toNormalizedString(),
+              cfg.getQueueSize(), cfg.isAutoFlush(), theWriter);
+        }
+        else
+        {
+          this.writer = new ParallelTextWriter("Parallel Text Writer for "
+              + cfg.dn().toNormalizedString(), cfg.isAutoFlush(), theWriter);
+        }
+      }
+      else
+      {
+        this.writer = theWriter;
+      }
+    }
+    catch (final DirectoryException e)
+    {
+      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)
+    {
+      final Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(
+          logFile.toString(), cfg.dn().toString(), String.valueOf(e));
+      throw new InitializationException(message, e);
+
+    }
+
+    this.cfg = cfg;
+    setTimeStampFormat(cfg.getLogRecordTimeFormat());
+
+    cfg.addFileBasedHTTPAccessChangeListener(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isConfigurationAcceptable(
+      final FileBasedHTTPAccessLogPublisherCfg configuration,
+      final List<Message> unacceptableReasons)
+  {
+    return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isConfigurationChangeAcceptable(
+      final FileBasedHTTPAccessLogPublisherCfg config,
+      final List<Message> unacceptableReasons)
+  {
+    // Validate the time-stamp formatter.
+    final String formatString = config.getLogRecordTimeFormat();
+    try
+    {
+       new SimpleDateFormat(formatString);
+    }
+    catch (final Exception e)
+    {
+      final Message message = ERR_CONFIG_LOGGING_INVALID_TIME_FORMAT.get(String
+          .valueOf(formatString));
+      unacceptableReasons.add(message);
+      return false;
+    }
+
+    // Make sure the permission is valid.
+    try
+    {
+      final FilePermission filePerm = FilePermission.decodeUNIXMode(config
+          .getLogFilePermissions());
+      if (!filePerm.isOwnerWritable())
+      {
+        final Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config
+            .getLogFilePermissions());
+        unacceptableReasons.add(message);
+        return false;
+      }
+    }
+    catch (final DirectoryException e)
+    {
+      final Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get(
+          config.getLogFilePermissions(), String.valueOf(e));
+      unacceptableReasons.add(message);
+      return false;
+    }
+
+    return true;
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  public final void close()
+  {
+    writer.shutdown();
+    TimeThread.removeUserDefinedFormatter(timeStampFormat);
+    if (cfg != null)
+    {
+      cfg.removeFileBasedHTTPAccessChangeListener(this);
+    }
+  }
+
+  private void setTimeStampFormat(String timeStampFormat)
+  {
+    this.timeStampFormat = timeStampFormat;
+    this.dateFormatter = new SimpleDateFormat(timeStampFormat);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public final DN getDN()
+  {
+    return cfg != null ? cfg.dn() : null;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void logRequestInfo(HTTPRequestInfo ri)
+  {
+    final StringBuilder sb = new StringBuilder(100);
+
+    // remotehost
+    if (ri.getRemoteHost() != null)
+    {
+      sb.append(ri.getRemoteHost());
+    }
+    else
+    {
+      sb.append(ri.getRemoteAddress());
+    }
+    // rfc931 - not supported
+    // authuser
+    sb.append(" ");
+    if (ri.getAuthUser() != null)
+    {
+      sb.append(ri.getAuthUser());
+    }
+    // [dateAndTime]
+    sb.append(" [").append(TimeThread.getUserDefinedTime(timeStampFormat))
+        .append("]");
+    // "request"
+    sb.append(" \"").append(ri.getMethod());
+    sb.append(" ").append(ri.getQuery());
+    sb.append(" ").append(ri.getProtocol()).append("\"");
+    // HTTP response status code
+    sb.append(" ").append(ri.getStatusCode());
+    // bytes - not supported
+    // "user agent"
+    sb.append(" \"").append(ri.getUserAgent()).append("\"");
+
+    writer.writeRecord(sb.toString());
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java b/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
index f03cc51..0adde11 100644
--- a/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
+++ b/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
@@ -44,11 +44,11 @@
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
 
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Connection;
@@ -66,6 +66,7 @@
 import org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory;
 import org.opends.messages.Message;
 import org.opends.server.admin.std.server.ConnectionHandlerCfg;
+import org.opends.server.loggers.HTTPRequestInfo;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.schema.SchemaConstants;
 import org.opends.server.types.AddressMask;
@@ -85,8 +86,8 @@
   private final class HTTPRequestContext
   {
     private AsyncContext asyncContext;
-    private ServletRequest request;
-    private ServletResponse response;
+    private HttpServletRequest request;
+    private HttpServletResponse response;
     private FilterChain chain;
 
     private HTTPClientConnection clientConnection;
@@ -94,8 +95,12 @@
 
     /** Whether to pretty print the resulting JSON. */
     private boolean prettyPrint;
-    /** Can be used for the bind request. */
+    /** Used for the bind request when credentials are specified. */
+    private String userName;
+    /** Used for the bind request when credentials are specified. */
     private String password;
+    /** Request information for logging. */
+    private HTTPRequestInfo requestInfo;
   }
 
   /**
@@ -176,6 +181,8 @@
     @Override
     public void handleResult(BindResult result)
     {
+      ctx.requestInfo.setAuthUser(ctx.userName);
+
       final AuthenticationInfo authInfo =
           new AuthenticationInfo(to(resultEntry), to(resultEntry.getName()),
               ByteString.valueOf(ctx.password), false);
@@ -231,23 +238,44 @@
 
   /** {@inheritDoc} */
   @Override
-  public void doFilter(ServletRequest request, ServletResponse response,
+  public void doFilter(ServletRequest req, ServletResponse resp,
       FilterChain chain)
   {
-    final boolean prettyPrint =
+    final HttpServletRequest request = (HttpServletRequest) req;
+    final HttpServletResponse response = (HttpServletResponse) resp;
+
+    final HTTPRequestContext ctx = new HTTPRequestContext();
+
+    ctx.request = request;
+    ctx.response = new HttpServletResponseWrapper(response)
+    {
+
+      /** {@inheritDoc} */
+      @Override
+      public void setStatus(int sc)
+      {
+        ctx.requestInfo.log(sc);
+        super.setStatus(sc);
+      }
+
+      /** {@inheritDoc} */
+      @Override
+      public void setStatus(int sc, String sm)
+      {
+        ctx.requestInfo.log(sc);
+        super.setStatus(sc, sm);
+      }
+    };
+    ctx.chain = chain;
+    ctx.prettyPrint =
         Boolean.parseBoolean(request.getParameter("_prettyPrint"));
+    ctx.requestInfo = new HTTPRequestInfo(ctx.request);
 
     final HTTPClientConnection clientConnection =
         new HTTPClientConnection(this.connectionHandler, request);
     this.connectionHandler.addClientConnection(clientConnection);
 
-    final HTTPRequestContext ctx = new HTTPRequestContext();
-    ctx.request = request;
-    ctx.response = response;
-    ctx.chain = chain;
-
     ctx.clientConnection = clientConnection;
-    ctx.prettyPrint = prettyPrint;
 
     try
     {
@@ -259,17 +287,18 @@
       // checked.
       logConnect(clientConnection);
 
-      ctx.connection = new SdkConnectionAdapter(clientConnection);
+      ctx.connection =
+          new SdkConnectionAdapter(clientConnection, ctx.requestInfo);
 
       final String[] userPassword = extractUsernamePassword(request);
       if (userPassword != null && userPassword.length == 2)
       {
-        final String userName = userPassword[0];
+        ctx.userName = userPassword[0];
         ctx.password = userPassword[1];
 
         ctx.asyncContext = getAsyncContext(request);
 
-        ctx.connection.searchSingleEntryAsync(buildSearchRequest(userName),
+        ctx.connection.searchSingleEntryAsync(buildSearchRequest(ctx.userName),
             new DoBindResultHandler(ctx));
       }
       else if (this.connectionHandler.acceptUnauthenticatedRequests())
@@ -308,13 +337,13 @@
 
   private void sendAuthenticationFailure(HTTPRequestContext ctx)
   {
+    final int statusCode = HttpServletResponse.SC_UNAUTHORIZED;
     try
     {
       // The user could not be authenticated. Send an HTTP Basic authentication
       // challenge if HTTP Basic authentication is enabled.
       ResourceException unauthorizedException =
-          ResourceException.getException(HttpServletResponse.SC_UNAUTHORIZED,
-              "Invalid Credentials");
+          ResourceException.getException(statusCode, "Invalid Credentials");
       sendErrorReponse(ctx.response, ctx.prettyPrint, unauthorizedException);
 
       ctx.clientConnection.disconnect(DisconnectReason.INVALID_CREDENTIALS,
@@ -322,6 +351,8 @@
     }
     finally
     {
+      ctx.requestInfo.log(statusCode);
+
       if (ctx.asyncContext != null)
       {
         ctx.asyncContext.complete();
@@ -331,6 +362,7 @@
 
   private void onFailure(Exception e, HTTPRequestContext ctx)
   {
+    ResourceException ex = Rest2LDAP.asResourceException(e);
     try
     {
       if (debugEnabled())
@@ -338,8 +370,7 @@
         TRACER.debugCaught(DebugLogLevel.ERROR, e);
       }
 
-      sendErrorReponse(ctx.response, ctx.prettyPrint, Rest2LDAP
-          .asResourceException(e));
+      sendErrorReponse(ctx.response, ctx.prettyPrint, ex);
 
       Message message =
           INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(ctx.clientConnection
@@ -352,6 +383,8 @@
     }
     finally
     {
+      ctx.requestInfo.log(ex.getCode());
+
       if (ctx.asyncContext != null)
       {
         ctx.asyncContext.complete();
@@ -359,7 +392,7 @@
     }
   }
 
-  private boolean canProcessRequest(ServletRequest request,
+  private boolean canProcessRequest(HttpServletRequest request,
       final HTTPClientConnection clientConnection) throws UnknownHostException
   {
     InetAddress clientAddr = InetAddress.getByName(request.getRemoteAddr());
@@ -415,20 +448,18 @@
    * @throws ResourceException
    *           if any error occur
    */
-  String[] extractUsernamePassword(ServletRequest request)
+  String[] extractUsernamePassword(HttpServletRequest request)
       throws ResourceException
   {
-    HttpServletRequest req = (HttpServletRequest) request;
-
     // TODO Use session to reduce hits with search + bind?
     // Use proxied authorization control for session.
 
     if (authConfig.isCustomHeadersAuthenticationSupported())
     {
       final String userName =
-          req.getHeader(authConfig.getCustomHeaderUsername());
+          request.getHeader(authConfig.getCustomHeaderUsername());
       final String password =
-          req.getHeader(authConfig.getCustomHeaderPassword());
+          request.getHeader(authConfig.getCustomHeaderPassword());
       if (userName != null && password != null)
       {
         return new String[] { userName, password };
@@ -437,7 +468,7 @@
 
     if (authConfig.isBasicAuthenticationSupported())
     {
-      String httpBasicAuthHeader = req.getHeader(HTTP_BASIC_AUTH_HEADER);
+      String httpBasicAuthHeader = request.getHeader(HTTP_BASIC_AUTH_HEADER);
       if (httpBasicAuthHeader != null)
       {
         String[] userPassword = parseUsernamePassword(httpBasicAuthHeader);
@@ -463,26 +494,23 @@
    * @param re
    *          the resource exception with the error response content
    */
-  void sendErrorReponse(ServletResponse response, boolean prettyPrint,
+  void sendErrorReponse(HttpServletResponse response, boolean prettyPrint,
       ResourceException re)
   {
-    HttpServletResponse resp = (HttpServletResponse) response;
-    resp.setStatus(re.getCode());
+    response.setStatus(re.getCode());
 
     if (re.getCode() == HttpServletResponse.SC_UNAUTHORIZED
         && authConfig.isBasicAuthenticationSupported())
     {
-      resp.setHeader("WWW-Authenticate",
+      response.setHeader("WWW-Authenticate",
           "Basic realm=\"org.forgerock.opendj\"");
     }
 
     try
     {
       // Send error JSON document out
-      resp.setHeader("Content-Type", "application/json");
-
-      ServletOutputStream out = resp.getOutputStream();
-      out.println(toJSON(prettyPrint, re));
+      response.setHeader("Content-Type", "application/json");
+      response.getOutputStream().println(toJSON(prettyPrint, re));
     }
     catch (IOException ignore)
     {
diff --git a/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java b/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
index 04188dc..ff2a1d5 100644
--- a/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
+++ b/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
@@ -33,6 +33,8 @@
 import java.util.LinkedHashSet;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
 import org.forgerock.opendj.ldap.ConnectionEventListener;
 import org.forgerock.opendj.ldap.ErrorResultException;
@@ -68,6 +70,7 @@
 import org.opends.server.core.QueueingStrategy;
 import org.opends.server.core.SearchOperationBasis;
 import org.opends.server.core.UnbindOperationBasis;
+import org.opends.server.loggers.HTTPRequestInfo;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.ByteString;
@@ -91,6 +94,9 @@
   /** The HTTP client connection being "adapted". */
   private final HTTPClientConnection clientConnection;
 
+  /** The HTTP request information to log. */
+  private final HTTPRequestInfo requestInfo;
+
   /**
    * The next message ID (and operation ID) that should be used for this
    * connection.
@@ -111,10 +117,14 @@
    *
    * @param clientConnection
    *          the HTTP client connection being "adapted"
+   * @param requestInfo
+   *          the HTTP request information to log
    */
-  public SdkConnectionAdapter(HTTPClientConnection clientConnection)
+  public SdkConnectionAdapter(HTTPClientConnection clientConnection,
+      HTTPRequestInfo requestInfo)
   {
     this.clientConnection = clientConnection;
+    this.requestInfo = requestInfo;
     this.queueingStrategy =
         new BoundedWorkQueueStrategy(clientConnection.getConnectionHandler()
             .getCurrentConfig().getMaxConcurrentOpsPerConnection());
@@ -223,6 +233,11 @@
     {
       this.clientConnection.disconnect(DisconnectReason.UNBIND, false, null);
     }
+
+    // At this point, we try to log the request with OK status code.
+    // If it was already logged, it will be a no op.
+    this.requestInfo.log(HttpServletResponse.SC_OK);
+
     isClosed = true;
   }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
index a7577ff..f1904d7 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -51,7 +51,14 @@
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Handler;
 import java.util.logging.LogManager;
@@ -78,8 +85,10 @@
 import org.opends.server.extensions.ConfigFileHandler;
 import org.opends.server.loggers.AccessLogger;
 import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.loggers.HTTPAccessLogger;
 import org.opends.server.loggers.TextAccessLogPublisher;
 import org.opends.server.loggers.TextErrorLogPublisher;
+import org.opends.server.loggers.TextHTTPAccessLogPublisher;
 import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.loggers.debug.TextDebugLogPublisher;
 import org.opends.server.plugins.InvocationCounterPlugin;
@@ -187,23 +196,17 @@
   public static final String PROPERTY_OPENDMK_LOCATION =
           "org.opends.server.snmp.opendmk";
 
-  /**
-   * The test text writer for the Debug Logger
-   */
-  public static TestTextWriter DEBUG_TEXT_WRITER =
-      new TestTextWriter();
+  /** The test text writer for the Debug Logger */
+  public static TestTextWriter DEBUG_TEXT_WRITER = new TestTextWriter();
 
-  /**
-   * The test text writer for the Debug Logger
-   */
-  public static TestTextWriter ERROR_TEXT_WRITER =
-      new TestTextWriter();
+  /** The test text writer for the Error Logger */
+  public static TestTextWriter ERROR_TEXT_WRITER = new TestTextWriter();
 
-  /**
-   * The test text writer for the Debug Logger
-   */
-  public static TestTextWriter ACCESS_TEXT_WRITER =
-      new TestTextWriter();
+  /** The test text writer for the Access Logger */
+  public static TestTextWriter ACCESS_TEXT_WRITER = new TestTextWriter();
+
+  /** The test text writer for the HTTP Access Logger */
+  public static TestTextWriter HTTP_ACCESS_TEXT_WRITER = new TestTextWriter();
 
   /**
    * Indicates whether the server has already been started.  The value of this
@@ -525,6 +528,9 @@
           TextAccessLogPublisher.getStartupTextAccessPublisher(
               ACCESS_TEXT_WRITER, false));
 
+      HTTPAccessLogger.addHTTPAccessLogPublisher(TextHTTPAccessLogPublisher
+          .getStartupTextHTTPAccessPublisher(HTTP_ACCESS_TEXT_WRITER));
+
       // Use more verbose tool logger.
       ErrorLogger.addErrorLogPublisher(
          TextErrorLogPublisher.getToolStartupTextErrorPublisher(

--
Gitblit v1.10.0