Moved component and receivers companion sources to the Chainsaw source tree - 
those companions won't be released.



Project: http://git-wip-us.apache.org/repos/asf/logging-chainsaw/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-chainsaw/commit/08c7be5c
Tree: http://git-wip-us.apache.org/repos/asf/logging-chainsaw/tree/08c7be5c
Diff: http://git-wip-us.apache.org/repos/asf/logging-chainsaw/diff/08c7be5c

Branch: refs/heads/master
Commit: 08c7be5cdb56374f77d297a0e90440aeef541b8a
Parents: decd6ab
Author: Scott Deboy <[email protected]>
Authored: Mon Oct 3 06:15:40 2011 +0000
Committer: Scott Deboy <[email protected]>
Committed: Mon Oct 3 06:15:40 2011 +0000

----------------------------------------------------------------------
 HOWTOBUILD.txt                                  |   13 -
 pom.xml                                         |   38 +-
 .../apache/log4j/LoggerRepositoryExImpl.java    |  697 ++++++++++++
 src/main/java/org/apache/log4j/ULogger.java     |  203 ++++
 .../org/apache/log4j/db/ConnectionSource.java   |   69 ++
 .../log4j/db/ConnectionSourceSkeleton.java      |  150 +++
 .../apache/log4j/db/CustomSQLDBReceiver.java    |  468 ++++++++
 .../java/org/apache/log4j/db/DBAppender.java    |  403 +++++++
 src/main/java/org/apache/log4j/db/DBHelper.java |   68 ++
 .../java/org/apache/log4j/db/DBReceiver.java    |  140 +++
 .../java/org/apache/log4j/db/DBReceiverJob.java |  230 ++++
 .../log4j/db/DataSourceConnectionSource.java    |  105 ++
 .../log4j/db/DriverManagerConnectionSource.java |  133 +++
 .../apache/log4j/db/JNDIConnectionSource.java   |  143 +++
 .../apache/log4j/db/dialect/HSQLDBDialect.java  |   31 +
 .../apache/log4j/db/dialect/MsSQLDialect.java   |   34 +
 .../apache/log4j/db/dialect/MySQLDialect.java   |   32 +
 .../apache/log4j/db/dialect/OracleDialect.java  |   33 +
 .../log4j/db/dialect/PostgreSQLDialect.java     |   35 +
 .../org/apache/log4j/db/dialect/SQLDialect.java |   27 +
 .../apache/log4j/db/dialect/SybaseDialect.java  |   32 +
 .../java/org/apache/log4j/db/dialect/Util.java  |  125 +++
 .../java/org/apache/log4j/db/dialect/db2.sql    |   64 ++
 .../java/org/apache/log4j/db/dialect/db2l.sql   |   61 +
 .../java/org/apache/log4j/db/dialect/hsqldb.sql |   60 +
 .../java/org/apache/log4j/db/dialect/mssql.sql  |   61 +
 .../java/org/apache/log4j/db/dialect/mysql.sql  |   71 ++
 .../java/org/apache/log4j/db/dialect/oracle.sql |   77 ++
 .../org/apache/log4j/db/dialect/postgresql.sql  |   63 ++
 src/main/java/org/apache/log4j/db/package.html  |   36 +
 .../org/apache/log4j/helpers/Constants.java     |  126 +++
 .../apache/log4j/helpers/MessageFormatter.java  |  154 +++
 .../apache/log4j/helpers/UtilLoggingLevel.java  |  238 ++++
 .../java/org/apache/log4j/net/AddressBased.java |   36 +
 .../java/org/apache/log4j/net/JMSReceiver.java  |  301 +++++
 .../apache/log4j/net/JMSReceiverBeanInfo.java   |   52 +
 .../org/apache/log4j/net/MulticastAppender.java |  345 ++++++
 .../org/apache/log4j/net/MulticastReceiver.java |  276 +++++
 .../log4j/net/MulticastReceiverBeanInfo.java    |   51 +
 .../java/org/apache/log4j/net/NetworkBased.java |   39 +
 .../java/org/apache/log4j/net/PortBased.java    |   34 +
 .../org/apache/log4j/net/SocketHubReceiver.java |  409 +++++++
 .../java/org/apache/log4j/net/SocketNode13.java |  299 +++++
 .../log4j/net/SocketNodeEventListener.java      |   43 +
 .../org/apache/log4j/net/SocketReceiver.java    |  479 ++++++++
 .../java/org/apache/log4j/net/UDPAppender.java  |  330 ++++++
 .../java/org/apache/log4j/net/UDPReceiver.java  |  281 +++++
 .../org/apache/log4j/net/XMLSocketNode.java     |  205 ++++
 .../org/apache/log4j/net/XMLSocketReceiver.java |  316 ++++++
 .../org/apache/log4j/plugins/Pauseable.java     |   39 +
 .../java/org/apache/log4j/plugins/Plugin.java   |  144 +++
 .../org/apache/log4j/plugins/PluginEvent.java   |   47 +
 .../apache/log4j/plugins/PluginListener.java    |   43 +
 .../apache/log4j/plugins/PluginRegistry.java    |  303 +++++
 .../apache/log4j/plugins/PluginSkeleton.java    |  222 ++++
 .../java/org/apache/log4j/plugins/Receiver.java |  131 +++
 .../apache/log4j/rewrite/MapRewritePolicy.java  |   85 ++
 .../log4j/rewrite/PropertyRewritePolicy.java    |   93 ++
 .../log4j/rewrite/ReflectionRewritePolicy.java  |   89 ++
 .../apache/log4j/rewrite/RewriteAppender.java   |  199 ++++
 .../org/apache/log4j/rewrite/RewritePolicy.java |   37 +
 .../java/org/apache/log4j/scheduler/Job.java    |   36 +
 .../org/apache/log4j/scheduler/Scheduler.java   |  307 ++++++
 .../java/org/apache/log4j/spi/Component.java    |   37 +
 .../org/apache/log4j/spi/ComponentBase.java     |  126 +++
 src/main/java/org/apache/log4j/spi/Decoder.java |   63 ++
 .../java/org/apache/log4j/spi/ErrorItem.java    |  172 +++
 .../java/org/apache/log4j/spi/Log4JULogger.java |  229 ++++
 .../apache/log4j/spi/LoggerEventListener.java   |   59 +
 .../spi/LoggerRepositoryEventListener.java      |   53 +
 .../apache/log4j/spi/LoggerRepositoryEx.java    |  198 ++++
 .../java/org/apache/log4j/spi/NOPULogger.java   |  200 ++++
 .../org/apache/log4j/spi/SimpleULogger.java     |  305 +++++
 .../org/apache/log4j/spi/Thresholdable.java     |   58 +
 .../apache/log4j/varia/ListModelAppender.java   |   79 ++
 .../log4j/varia/LogFilePatternReceiver.java     | 1043 ++++++++++++++++++
 .../varia/LogFilePatternReceiverBeanInfo.java   |   53 +
 .../apache/log4j/xml/LogFileXMLReceiver.java    |  310 ++++++
 .../log4j/xml/UtilLoggingEntityResolver.java    |   50 +
 .../apache/log4j/xml/UtilLoggingXMLDecoder.java |  468 ++++++++
 .../java/org/apache/log4j/xml/XMLDecoder.java   |  495 +++++++++
 .../log4j/chainsaw/help/release-notes.html      |    1 +
 .../java/org/apache/log4j/VectorAppender.java   |   91 ++
 .../org/apache/log4j/db/FullCycleDBTest.java    |  327 ++++++
 .../log4j/helpers/UtilLoggingLevelTest.java     |   46 +
 .../log4j/rewrite/RewriteAppenderTest.java      |  132 +++
 .../java/org/apache/log4j/util/Compare.java     |  202 ++++
 .../org/apache/log4j/xml/XMLDecoderTest.java    |   86 ++
 .../log4j/db/append-with-drivermanager1.xml     |   48 +
 .../log4j/db/read-with-drivermanager1.xml       |   55 +
 .../resources/org/apache/log4j/rewrite/map.log  |    3 +
 .../resources/org/apache/log4j/rewrite/map.xml  |   38 +
 .../org/apache/log4j/rewrite/property.log       |    2 +
 .../org/apache/log4j/rewrite/property.xml       |   40 +
 .../org/apache/log4j/rewrite/reflection.log     |    3 +
 .../org/apache/log4j/rewrite/reflection.xml     |   38 +
 .../org/apache/log4j/xml/xmlLayout.1.xml        |  162 +++
 .../org/apache/log4j/xml/xsltLayout.1.xml       |  139 +++
 98 files changed, 14874 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/HOWTOBUILD.txt
----------------------------------------------------------------------
diff --git a/HOWTOBUILD.txt b/HOWTOBUILD.txt
index 7375489..ebbd4bb 100644
--- a/HOWTOBUILD.txt
+++ b/HOWTOBUILD.txt
@@ -23,19 +23,6 @@ Firstly, you'll need maven 2.0.9+ to build Chainsaw:
 http://maven.apache.org
 
 
-Next, an interim step is required until the 'companions' are voted on and 
released:
-
-* SVN check out the following projects:
-       log4j-component - 
http://svn.apache.org/repos/asf/logging/log4j/companions/component/trunk/
-       log4j-extras - 
http://svn.apache.org/repos/asf/logging/log4j/companions/extras/trunk/
-       log4j-receivers - 
http://svn.apache.org/repos/asf/logging/log4j/companions/receivers/trunk/
-
-  In each of these checkout directories:
-       mvn install
-
-  This will install into your local maven repository the correct releases of 
the required dependencies. 
-  These dependencies are not yet available in the standard maven repositories, 
and so failure to complete this step will prevent you from building Chainsaw.
-
 * cd to Chainsaw project checkout directory
 
        mvn install

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f434fcd..18c0f43 100644
--- a/pom.xml
+++ b/pom.xml
@@ -118,8 +118,7 @@
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
-          <source>1.2</source>
-          <target>1.1</target>
+          <source>1.4</source>
         </configuration>
       </plugin>
       <plugin>
@@ -220,7 +219,7 @@
             <artifactId>ant-contrib</artifactId>
             <version>1.0b2</version>
           </dependency>
-        </dependencies>
+       </dependencies>
       </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
@@ -324,6 +323,14 @@
           <iconFile>${basedir}/src/main/resources/logo.icns</iconFile>
           <bundleName>Chainsaw</bundleName>
         </configuration>
+        <executions>
+            <execution>
+                <phase>package</phase>
+                <goals>
+                    <goal>bundle</goal>
+                </goals>
+            </execution>
+       </executions>
       </plugin>
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
@@ -386,17 +393,6 @@
     </dependency>
     <dependency>
       <groupId>log4j</groupId>
-      <artifactId>apache-log4j-receivers</artifactId>
-      <version>1.0-SNAPSHOT</version>
-    </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>apache-log4j-receivers</artifactId>
-      <version>1.0-SNAPSHOT</version>
-      <classifier>javadoc</classifier>
-    </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
       <version>1.2.16</version>
     </dependency>
@@ -437,6 +433,18 @@
       <artifactId>jsch</artifactId>
       <version>0.1.42</version>
     </dependency>
+    <dependency>
+               <groupId>hsqldb</groupId>
+               <artifactId>hsqldb</artifactId>
+               <version>1.8.0.7</version>
+               <scope>test</scope>
+       </dependency>
+    <dependency>
+        <groupId>org.apache.geronimo.specs</groupId>
+        <artifactId>geronimo-jms_1.1_spec</artifactId>
+        <version>1.0</version>
+        <optional>true</optional>
+    </dependency>
   </dependencies>
   <reporting>
     <excludeDefaults>true</excludeDefaults>
@@ -480,7 +488,7 @@
       </plugin>
     </plugins>
   </reporting>
-  <distributionManagement>
+    <distributionManagement>
     <repository>
       <id>logging.repo</id>
       
<url>scp://people.apache.org/www/people.apache.org/builds/logging/repo/</url>

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/LoggerRepositoryExImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/LoggerRepositoryExImpl.java 
b/src/main/java/org/apache/log4j/LoggerRepositoryExImpl.java
new file mode 100644
index 0000000..c2f5aaf
--- /dev/null
+++ b/src/main/java/org/apache/log4j/LoggerRepositoryExImpl.java
@@ -0,0 +1,697 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererMap;
+import org.apache.log4j.plugins.Plugin;
+import org.apache.log4j.plugins.PluginRegistry;
+import org.apache.log4j.scheduler.Scheduler;
+import org.apache.log4j.spi.ErrorItem;
+import org.apache.log4j.spi.HierarchyEventListener;
+import org.apache.log4j.spi.LoggerEventListener;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggerRepositoryEventListener;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * This class implements LoggerRepositoryEx by
+ *   wrapping an existing LoggerRepository implementation
+ *   and implementing the newly added capabilities.
+*/
+public final class LoggerRepositoryExImpl
+        implements LoggerRepositoryEx,
+        RendererSupport,
+        UnrecognizedElementHandler {
+
+    /**
+     * Wrapped logger repository.
+     */
+  private final LoggerRepository repo;
+
+    /**
+     * Logger factory.  Does not affect class of logger
+     * created by underlying repository.
+     */
+  private LoggerFactory loggerFactory;
+
+    /**
+     *  Renderer support.
+     */
+  private final RendererSupport rendererSupport;
+
+    /**
+     * List of repository event listeners.
+     */
+  private final ArrayList repositoryEventListeners = new ArrayList();
+    /**
+     * Map of HierarchyEventListener keyed by LoggingEventListener.
+     */
+  private final Map loggerEventListeners = new HashMap();
+    /**
+     * Name of hierarchy.
+     */
+  private String name;
+    /**
+     * Plug in registry.
+     */
+  private PluginRegistry pluginRegistry;
+    /**
+     * Properties.
+     */
+  private final Map properties = new Hashtable();
+    /**
+     * Scheduler.
+     */
+  private Scheduler scheduler;
+
+  /** The repository can also be used as an object store
+   * for various objects used by log4j components.
+   */
+  private Map objectMap = new HashMap();
+
+
+    /**
+     * Error list.
+     */
+  private List errorList = new Vector();
+
+    /**
+     * True if hierarchy has not been modified.
+     */
+  private boolean pristine = true;
+
+  /**
+     Constructs a new logger hierarchy.
+
+     @param repository Base implementation of repository.
+
+   */
+  public LoggerRepositoryExImpl(final LoggerRepository repository) {
+    super();
+    if (repository == null) {
+        throw new NullPointerException("repository");
+    }
+    repo = repository;
+    if (repository instanceof RendererSupport) {
+        rendererSupport = (RendererSupport) repository;
+    } else {
+        rendererSupport = new RendererSupportImpl();
+    }
+  }
+
+
+  /**
+    Add a {@link LoggerRepositoryEventListener} to the repository. The
+    listener will be called when repository events occur.
+    @param listener listener
+    */
+  public void addLoggerRepositoryEventListener(
+    final LoggerRepositoryEventListener listener) {
+    synchronized (repositoryEventListeners) {
+      if (repositoryEventListeners.contains(listener)) {
+        LogLog.warn(
+          "Ignoring attempt to add a previously "
+                  + "registered LoggerRepositoryEventListener.");
+      } else {
+        repositoryEventListeners.add(listener);
+      }
+    }
+  }
+
+
+  /**
+    Remove a {@link LoggerRepositoryEventListener} from the repository.
+    @param listener listener
+    */
+  public void removeLoggerRepositoryEventListener(
+    final LoggerRepositoryEventListener listener) {
+    synchronized (repositoryEventListeners) {
+      if (!repositoryEventListeners.contains(listener)) {
+        LogLog.warn(
+          "Ignoring attempt to remove a "
+                  + "non-registered LoggerRepositoryEventListener.");
+      } else {
+        repositoryEventListeners.remove(listener);
+      }
+    }
+  }
+
+  /**
+    Add a {@link LoggerEventListener} to the repository. The  listener
+    will be called when repository events occur.
+    @param listener listener
+   */
+  public void addLoggerEventListener(final LoggerEventListener listener) {
+    synchronized (loggerEventListeners) {
+      if (loggerEventListeners.get(listener) != null) {
+        LogLog.warn(
+         "Ignoring attempt to add a previously registerd 
LoggerEventListener.");
+      } else {
+        HierarchyEventListenerProxy proxy =
+                new HierarchyEventListenerProxy(listener);
+        loggerEventListeners.put(listener, proxy);
+        repo.addHierarchyEventListener(proxy);
+      }
+    }
+  }
+
+    /**
+       Add a {@link org.apache.log4j.spi.HierarchyEventListener}
+     event to the repository.
+     @param listener listener
+       @deprecated Superceded by addLoggerEventListener
+    */
+    public
+    void addHierarchyEventListener(final HierarchyEventListener listener) {
+        repo.addHierarchyEventListener(listener);
+    }
+
+
+  /**
+    Remove a {@link LoggerEventListener} from the repository.
+    @param listener listener to be removed
+    */
+  public void removeLoggerEventListener(final LoggerEventListener listener) {
+    synchronized (loggerEventListeners) {
+      HierarchyEventListenerProxy proxy =
+              (HierarchyEventListenerProxy) loggerEventListeners.get(listener);
+      if (proxy == null) {
+        LogLog.warn(
+          "Ignoring attempt to remove a non-registered LoggerEventListener.");
+      } else {
+        loggerEventListeners.remove(listener);
+        proxy.disable();
+      }
+    }
+  }
+
+    /**
+     * Issue warning that there are no appenders in hierarchy.
+     * @param cat logger, not currently used.
+     */
+  public void emitNoAppenderWarning(final Category cat) {
+    repo.emitNoAppenderWarning(cat);
+  }
+
+  /**
+     Check if the named logger exists in the hierarchy. If so return
+     its reference, otherwise returns <code>null</code>.
+
+     @param loggerName The name of the logger to search for.
+     @return true if logger exists.
+  */
+  public Logger exists(final String loggerName) {
+    return repo.exists(loggerName);
+  }
+
+  /**
+   * Return the name of this hierarchy.
+   * @return name of hierarchy
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Set the name of this repository.
+   *
+   * Note that once named, a repository cannot be rerenamed.
+   * @param repoName name of hierarchy
+   */
+  public void setName(final String repoName) {
+    if (name == null) {
+      name = repoName;
+    } else if (!name.equals(repoName)) {
+      throw new IllegalStateException(
+        "Repository [" + name + "] cannot be renamed as [" + repoName + "].");
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map getProperties() {
+    return properties;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getProperty(final String key) {
+     return (String) properties.get(key);
+  }
+
+  /**
+   * Set a property by key and value. The property will be shared by all
+   * events in this repository.
+   * @param key property name
+   * @param value property value
+   */
+  public void setProperty(final String key,
+                          final String value) {
+   properties.put(key, value);
+  }
+
+  /**
+     The string form of {@link #setThreshold(Level)}.
+   @param levelStr symbolic name for level
+  */
+  public void setThreshold(final String levelStr) {
+    repo.setThreshold(levelStr);
+  }
+
+  /**
+     Enable logging for logging requests with level <code>l</code> or
+     higher. By default all levels are enabled.
+
+     @param l The minimum level for which logging requests are sent to
+     their appenders.  */
+  public void setThreshold(final Level l) {
+    repo.setThreshold(l);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public PluginRegistry getPluginRegistry() {
+   if (pluginRegistry == null) {
+     pluginRegistry = new PluginRegistry(this);
+   }
+   return pluginRegistry;
+  }
+
+
+    /**
+      Requests that a appender added event be sent to any registered
+      {@link LoggerEventListener}.
+      @param logger The logger to which the appender was added.
+      @param appender The appender added to the logger.
+     */
+    public void fireAddAppenderEvent(final Category logger,
+                                     final Appender appender) {
+        repo.fireAddAppenderEvent(logger, appender);
+    }
+
+
+    /**
+      Requests that a appender removed event be sent to any registered
+      {@link LoggerEventListener}.
+      @param logger The logger from which the appender was removed.
+      @param appender The appender removed from the logger.
+      */
+    public void fireRemoveAppenderEvent(final Category logger,
+                                        final Appender appender) {
+       if (repo instanceof Hierarchy) {
+           ((Hierarchy) repo).fireRemoveAppenderEvent(logger, appender);
+       }
+    }
+
+
+  /**
+    Requests that a level changed event be sent to any registered
+    {@link LoggerEventListener}.
+    @param logger The logger which changed levels.
+    */
+  public void fireLevelChangedEvent(final Logger logger) {
+  }
+
+  /**
+   *
+   * Requests that a configuration changed event be sent to any registered
+   * {@link LoggerRepositoryEventListener}.
+   * 
+   */
+  public void fireConfigurationChangedEvent() {
+  }
+
+
+  /**
+     Returns the current threshold.
+     @return current threshold level
+
+     @since 1.2 */
+  public Level getThreshold() {
+    return repo.getThreshold();
+  }
+
+
+  /**
+     Return a new logger instance named as the first parameter using
+     the default factory.
+
+     <p>If a logger of that name already exists, then it will be
+     returned.  Otherwise, a new logger will be instantiated and
+     then linked with its existing ancestors as well as children.
+
+     @param loggerName The name of the logger to retrieve.
+     @return logger
+
+  */
+  public Logger getLogger(final String loggerName) {
+    return repo.getLogger(loggerName);
+  }
+
+  /**
+      Return a new logger instance named as the first parameter using
+      <code>factory</code>.
+
+      <p>If a logger of that name already exists, then it will be
+      returned.  Otherwise, a new logger will be instantiated by the
+      <code>factory</code> parameter and linked with its existing
+      ancestors as well as children.
+
+      @param loggerName The name of the logger to retrieve.
+      @param factory The factory that will make the new logger instance.
+      @return logger
+
+  */
+  public Logger getLogger(final String loggerName,
+                          final LoggerFactory factory) {
+    return repo.getLogger(loggerName, factory);
+  }
+
+  /**
+     Returns all the currently defined categories in this hierarchy as
+     an {@link java.util.Enumeration Enumeration}.
+
+     <p>The root logger is <em>not</em> included in the returned
+     {@link Enumeration}.
+     @return enumerator of current loggers
+   */
+  public Enumeration getCurrentLoggers() {
+    return repo.getCurrentLoggers();
+  }
+
+  /**
+   * Return the the list of previously encoutered {@link ErrorItem error 
items}.
+   * @return list of errors
+   */
+  public List getErrorList() {
+    return errorList;
+  }
+
+  /**
+   * Add an error item to the list of previously encountered errors.
+   * @param errorItem error to add to list of errors.
+   */
+  public void addErrorItem(final ErrorItem errorItem) {
+    getErrorList().add(errorItem);
+  }
+
+  /**
+   * Get enumerator over current loggers.
+   * @return enumerator over current loggers
+     @deprecated Please use {@link #getCurrentLoggers} instead.
+   */
+  public Enumeration getCurrentCategories() {
+    return repo.getCurrentCategories();
+  }
+
+  /**
+     Get the renderer map for this hierarchy.
+   @return renderer map
+  */
+  public RendererMap getRendererMap() {
+    return rendererSupport.getRendererMap();
+  }
+
+  /**
+     Get the root of this hierarchy.
+
+     @since 0.9.0
+   @return root of hierarchy
+   */
+  public Logger getRootLogger() {
+    return repo.getRootLogger();
+  }
+
+  /**
+     This method will return <code>true</code> if this repository is
+     disabled for <code>level</code> value passed as parameter and
+     <code>false</code> otherwise. See also the {@link
+     #setThreshold(Level) threshold} method.
+   @param level numeric value for level.
+   @return true if disabled for specified level
+   */
+  public boolean isDisabled(final int level) {
+    return repo.isDisabled(level);
+  }
+
+  /**
+     Reset all values contained in this hierarchy instance to their
+     default.  This removes all appenders from all categories, sets
+     the level of all non-root categories to <code>null</code>,
+     sets their additivity flag to <code>true</code> and sets the level
+     of the root logger to DEBUG.  Moreover,
+     message disabling is set its default "off" value.
+
+     <p>Existing categories are not removed. They are just reset.
+
+     <p>This method should be used sparingly and with care as it will
+     block all logging until it is completed.</p>
+
+     @since 0.8.5 */
+  public void resetConfiguration() {
+    repo.resetConfiguration();
+  }
+
+  /**
+     Used by subclasses to add a renderer to the hierarchy passed as parameter.
+   @param renderedClass class
+   @param renderer object used to render class.
+   */
+  public void setRenderer(final Class renderedClass,
+                          final ObjectRenderer renderer) {
+    rendererSupport.setRenderer(renderedClass, renderer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isPristine() {
+    return pristine;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setPristine(final boolean state) {
+    pristine = state;
+  }
+
+  /**
+     Shutting down a hierarchy will <em>safely</em> close and remove
+     all appenders in all categories including the root logger.
+
+     <p>Some appenders such as org.apache.log4j.net.SocketAppender
+     and AsyncAppender need to be closed before the
+     application exists. Otherwise, pending logging events might be
+     lost.
+
+     <p>The <code>shutdown</code> method is careful to close nested
+     appenders before closing regular appenders. This is allows
+     configurations where a regular appender is attached to a logger
+     and again to a nested appender.
+
+     @since 1.0 */
+  public void shutdown() {
+    repo.shutdown();
+  }
+
+
+  /**
+   * Return this repository's own scheduler.
+   * The scheduler is lazily instantiated.
+   * @return this repository's own scheduler.
+   */
+  public Scheduler getScheduler() {
+    if (scheduler == null) {
+      scheduler = new Scheduler();
+      scheduler.setDaemon(true);
+      scheduler.start();
+    }
+    return scheduler;
+  }
+
+    /**
+     * Puts object by key.
+     * @param key key, may not be null.
+     * @param value object to associate with key.
+     */
+  public void putObject(final String key,
+                        final Object value) {
+    objectMap.put(key, value);
+  }
+
+    /**
+     * Get object by key.
+     * @param key key, may not be null.
+     * @return object associated with key or null.
+     */
+  public Object getObject(final String key) {
+    return objectMap.get(key);
+  }
+
+    /**
+     * Set logger factory.
+     * @param factory logger factory.
+     */
+  public void setLoggerFactory(final LoggerFactory factory) {
+    if (factory == null) {
+      throw new NullPointerException();
+    }
+    this.loggerFactory = factory;
+  }
+
+    /**
+     * Get logger factory.
+     * @return logger factory.
+     */
+  public LoggerFactory getLoggerFactory() {
+    return loggerFactory;
+  }
+
+    /** {@inheritDoc} */
+    public boolean parseUnrecognizedElement(
+            final Element element,
+            final Properties props) throws Exception {
+        if ("plugin".equals(element.getNodeName())) {
+            Object instance =
+                    DOMConfigurator.parseElement(element, props, Plugin.class);
+            if (instance instanceof Plugin) {
+                Plugin plugin = (Plugin) instance;
+                String pluginName = 
DOMConfigurator.subst(element.getAttribute("name"), props);
+                if (pluginName.length() > 0) {
+                    plugin.setName(pluginName);
+                }
+                getPluginRegistry().addPlugin(plugin);
+                plugin.setLoggerRepository(this);
+
+                LogLog.debug("Pushing plugin on to the object stack.");
+                plugin.activateOptions();
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+
+    /**
+     * Implementation of RendererSupportImpl if not
+     * provided by LoggerRepository.
+     */
+   private static final class RendererSupportImpl implements RendererSupport {
+        /**
+         * Renderer map.
+         */
+       private final RendererMap renderers = new RendererMap();
+
+        /**
+         * Create new instance.
+         */
+       public RendererSupportImpl() {
+           super();
+       }
+
+        /** {@inheritDoc} */
+       public RendererMap getRendererMap() {
+           return renderers;
+       }
+
+        /** {@inheritDoc} */
+       public void setRenderer(final Class renderedClass,
+                               final ObjectRenderer renderer) {
+           renderers.put(renderedClass, renderer);
+       }
+   }
+
+    /**
+     * Proxy that implements HierarchyEventListener
+     * and delegates to LoggerEventListener.
+     */
+   private static final class HierarchyEventListenerProxy
+        implements HierarchyEventListener {
+        /**
+         * Wrapper listener.
+         */
+       private LoggerEventListener listener;
+
+        /**
+         * Creates new instance.
+         * @param l listener
+         */
+       public HierarchyEventListenerProxy(final LoggerEventListener l) {
+           super();
+           if (l == null) {
+               throw new NullPointerException("l");
+           }
+           listener = l;
+       }
+
+        /** {@inheritDoc} */
+       public void addAppenderEvent(final Category cat,
+                                    final Appender appender) {
+           if (isEnabled() && cat instanceof Logger) {
+                listener.appenderAddedEvent((Logger) cat, appender);
+           }
+       }
+
+        /** {@inheritDoc} */
+       public void removeAppenderEvent(final Category cat,
+                                    final Appender appender) {
+           if (isEnabled() && cat instanceof Logger) {
+                listener.appenderRemovedEvent((Logger) cat, appender);
+           }
+       }
+
+        /**
+         * Disable forwarding of notifications to
+         * simulate removal of listener.
+         */
+       public synchronized void disable() {
+           listener = null;
+       }
+
+        /**
+         * Gets whether proxy is enabled.
+         * @return true if proxy is enabled.
+         */
+       private synchronized boolean isEnabled() {
+           return listener != null;
+       }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/ULogger.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/ULogger.java 
b/src/main/java/org/apache/log4j/ULogger.java
new file mode 100644
index 0000000..83a6bb8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/ULogger.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+
+/**
+ * A proxy for org.slf4j.ULogger.  In slf4j implementing builds, this
+ *     interface will extend org.slf4j.ULogger and add no additional methods.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Curt Arnold
+ */
+ public interface ULogger {
+
+
+  /**
+   * Is the logger instance enabled for the DEBUG level?
+   * @return true if debug is enabled.
+   */
+  boolean isDebugEnabled();
+
+  /**
+   * Log a message object with the DEBUG level.
+   * @param msg - the message object to be logged
+   */
+  void debug(Object msg);
+
+
+  /**
+   * Log a parameterized message object at the DEBUG level.
+   *
+   * <p>This form is useful in avoiding the superflous object creation
+   * problem when invoking this method while it is disabled.
+   * </p>
+   * @param parameterizedMsg - the parameterized message object
+   * @param param1 - the parameter
+   */
+  void debug(Object parameterizedMsg, Object param1);
+
+  /**
+   * Log a parameterized message object at the DEBUG level.
+   *
+   * <p>This form is useful in avoiding the superflous object creation
+   * problem when invoking this method while it is disabled.
+   * </p>
+   * @param parameterizedMsg - the parameterized message object
+   * @param param1 - the first parameter
+   * @param param2 - the second parameter
+   */
+  void debug(String parameterizedMsg, Object param1, Object param2);
+    /**
+     * Log a message object with the <code>DEBUG</code> level including the
+     * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+     *
+     *
+     * @param msg the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+  void debug(Object msg, Throwable t);
+
+
+    /**
+     * Is the logger instance enabled for the INFO level?
+     * @return true if debug is enabled.
+     */
+  boolean isInfoEnabled();
+    /**
+     * Log a message object with the INFO level.
+     * @param msg - the message object to be logged
+     */
+  void info(Object msg);
+    /**
+     * Log a parameterized message object at the INFO level.
+     *
+     * <p>This form is useful in avoiding the superflous object creation
+     * problem when invoking this method while it is disabled.
+     * </p>
+     * @param parameterizedMsg - the parameterized message object
+     * @param param1 - the parameter
+     */
+  void info(Object parameterizedMsg, Object param1);
+    /**
+     * Log a parameterized message object at the INFO level.
+     *
+     * <p>This form is useful in avoiding the superflous object creation
+     * problem when invoking this method while it is disabled.
+     * </p>
+     * @param parameterizedMsg - the parameterized message object
+     * @param param1 - the first parameter
+     * @param param2 - the second parameter
+     */
+  void info(String parameterizedMsg, Object param1, Object param2);
+    /**
+     * Log a message object with the <code>INFO</code> level including the
+     * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+     *
+     *
+     * @param msg the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+  void info(Object msg, Throwable t);
+
+
+    /**
+     * Is the logger instance enabled for the WARN level?
+     * @return true if debug is enabled.
+     */
+  boolean isWarnEnabled();
+    /**
+     * Log a message object with the WARN level.
+     * @param msg - the message object to be logged
+     */
+  void warn(Object msg);
+    /**
+     * Log a parameterized message object at the WARN level.
+     *
+     * <p>This form is useful in avoiding the superflous object creation
+     * problem when invoking this method while it is disabled.
+     * </p>
+     * @param parameterizedMsg - the parameterized message object
+     * @param param1 - the parameter
+     */
+  void warn(Object parameterizedMsg, Object param1);
+    /**
+     * Log a parameterized message object at the WARN level.
+     *
+     * <p>This form is useful in avoiding the superflous object creation
+     * problem when invoking this method while it is disabled.
+     * </p>
+     * @param parameterizedMsg - the parameterized message object
+     * @param param1 - the first parameter
+     * @param param2 - the second parameter
+     */
+  void warn(String parameterizedMsg, Object param1, Object param2);
+    /**
+     * Log a message object with the <code>WARN</code> level including the
+     * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+     *
+     *
+     * @param msg the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+  void warn(Object msg, Throwable t);
+
+
+    /**
+     * Is the logger instance enabled for the ERROR level?
+     * @return true if debug is enabled.
+     */
+  boolean isErrorEnabled();
+    /**
+     * Log a message object with the ERROR level.
+     * @param msg - the message object to be logged
+     */
+  void error(Object msg);
+    /**
+     * Log a parameterized message object at the ERROR level.
+     *
+     * <p>This form is useful in avoiding the superflous object creation
+     * problem when invoking this method while it is disabled.
+     * </p>
+     * @param parameterizedMsg - the parameterized message object
+     * @param param1 - the parameter
+     */
+  void error(Object parameterizedMsg, Object param1);
+    /**
+     * Log a parameterized message object at the ERROR level.
+     *
+     * <p>This form is useful in avoiding the superflous object creation
+     * problem when invoking this method while it is disabled.
+     * </p>
+     * @param parameterizedMsg - the parameterized message object
+     * @param param1 - the first parameter
+     * @param param2 - the second parameter
+     */
+  void error(String parameterizedMsg, Object param1, Object param2);
+
+    /**
+     * Log a message object with the <code>ERROR</code> level including the
+     * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+     *
+     *
+     * @param msg the message object to log.
+     * @param t the exception to log, including its stack trace.
+     */
+    void error(Object msg, Throwable t);
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/db/ConnectionSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/db/ConnectionSource.java 
b/src/main/java/org/apache/log4j/db/ConnectionSource.java
new file mode 100644
index 0000000..0578734
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/ConnectionSource.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import org.apache.log4j.spi.Component;
+import org.apache.log4j.spi.OptionHandler;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+
+/**
+ *  The <id>ConnectionSource</id> interface provides a pluggable means of
+ *  transparently obtaining JDBC {@link java.sql.Connection}s for log4j classes
+ *  that require the use of a {@link java.sql.Connection}.
+ *
+ *  @author <a href="mailto:[email protected]";>Ray DeCampo</a>
+ */
+public interface ConnectionSource extends Component, OptionHandler {
+
+  final int UNKNOWN_DIALECT = 0;
+  final int POSTGRES_DIALECT = 1;
+  final int MYSQL_DIALECT = 2;
+  final int ORACLE_DIALECT = 3;
+  final int MSSQL_DIALECT = 4;
+  final int HSQL_DIALECT = 5;  
+  /**
+   *  Obtain a {@link java.sql.Connection} for use.  The client is
+   *  responsible for closing the {@link java.sql.Connection} when it is no
+   *  longer required.
+   *
+   *  @throws SQLException  if a {@link java.sql.Connection} could not be
+   *                        obtained
+   */
+  Connection getConnection() throws SQLException;
+
+  /**
+   * Get the SQL dialect that should be used for this connection. Note that the
+   * dialect is not needed if the JDBC driver supports the getGeneratedKeys 
+   * method.
+   */
+  int getSQLDialectCode();
+  
+  /**
+   * If the connection supports the JDBC 3.0 getGeneratedKeys method, then
+   * we do not need any specific dialect support.
+   */
+  boolean supportsGetGeneratedKeys();
+  
+  /**
+   * If the connection does not support batch updates, we will avoid using 
them.
+   */  
+  public boolean supportsBatchUpdates();
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/db/ConnectionSourceSkeleton.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/db/ConnectionSourceSkeleton.java 
b/src/main/java/org/apache/log4j/db/ConnectionSourceSkeleton.java
new file mode 100644
index 0000000..a5abecb
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/ConnectionSourceSkeleton.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+import org.apache.log4j.db.dialect.Util;
+import org.apache.log4j.spi.ComponentBase;
+
+
+/**
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public abstract class ConnectionSourceSkeleton extends ComponentBase 
implements ConnectionSource {
+  
+  private Boolean overriddenSupportsGetGeneratedKeys = null;
+  
+  private String user = null;
+  private String password = null;
+
+  // initially we have an unkonw dialect
+  private int dialectCode = UNKNOWN_DIALECT;
+  private boolean supportsGetGeneratedKeys = false;
+  private boolean supportsBatchUpdates = false;
+
+
+  /**
+   * Learn relevant information about this connection source.
+   *
+   */
+  public void discoverConnnectionProperties() {
+    Connection connection = null;
+    try {
+      connection = getConnection();
+      if (connection == null) {
+        getLogger().warn("Could not get a conneciton");
+        return;
+      }
+      DatabaseMetaData meta = connection.getMetaData();
+      Util util = new Util();
+      util.setLoggerRepository(repository);
+      if (overriddenSupportsGetGeneratedKeys != null) {
+        supportsGetGeneratedKeys = overriddenSupportsGetGeneratedKeys
+            .booleanValue();
+      } else {
+        supportsGetGeneratedKeys = util.supportsGetGeneratedKeys(meta);
+      }
+      supportsBatchUpdates = util.supportsBatchUpdates(meta);
+      dialectCode = Util.discoverSQLDialect(meta);
+    } catch (SQLException se) {
+      getLogger().warn("Could not discover the dialect to use.", se);
+    } finally {
+      DBHelper.closeConnection(connection);
+    }
+  }
+
+  /**
+   * Does this connection support the JDBC Connection.getGeneratedKeys method?
+   */
+  public final boolean supportsGetGeneratedKeys() {
+    return supportsGetGeneratedKeys;
+  }
+
+  public final int getSQLDialectCode() {
+    return dialectCode;
+  }
+
+  /**
+   * Get the password for this connection source.
+   */
+  public final String getPassword() {
+    return password;
+  }
+
+  /**
+   * Sets the password.
+   * @param password The password to set
+   */
+  public final void setPassword(final String password) {
+    this.password = password;
+  }
+
+  /**
+   * Get the user for this connection source.
+   */
+  public final String getUser() {
+    return user;
+  }
+
+  /**
+   * Sets the username.
+   * @param username The username to set
+   */
+  public final void setUser(final String username) {
+    this.user = username;
+  }
+
+  /**
+   * Returns the "overridden" value of "supportsGetGeneratedKeys" property of
+   * the JDBC driver. In certain cases, getting (e.g. Oracle 10g) generated 
keys
+   * does not work because it returns the ROWID, not the value of the sequence.
+   * 
+   * @return A non null string, with "true" or "false" value, if overridden,
+   *         <code>null</code> if not overridden.
+   */
+  public String getOverriddenSupportsGetGeneratedKeys() {
+    return overriddenSupportsGetGeneratedKeys != null ? 
overriddenSupportsGetGeneratedKeys
+        .toString()
+        : null;
+  }
+
+  /**
+   * Sets the "overridden" value of "supportsGetGeneratedKeys" property of the
+   * JDBC driver. In certain cases, getting (e.g. Oracle 10g) generated keys
+   * does not work because it returns the ROWID, not the value of the sequence.
+   * 
+   * @param overriddenSupportsGetGeneratedKeys
+   *          A non null string, with "true" or "false" value, if overridden,
+   *          <code>null</code> if not overridden.
+   */
+  public void setOverriddenSupportsGetGeneratedKeys(
+      String overriddenSupportsGetGeneratedKeys) {
+    this.overriddenSupportsGetGeneratedKeys = Boolean
+        .valueOf(overriddenSupportsGetGeneratedKeys);
+  }
+  
+  /**
+   * Does this connection support batch updates?
+   */
+  public final boolean supportsBatchUpdates() {
+    return supportsBatchUpdates;
+  }
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java 
b/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java
new file mode 100644
index 0000000..6c2d78b
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java
@@ -0,0 +1,468 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.scheduler.Job;
+import org.apache.log4j.scheduler.Scheduler;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+/**
+ * Converts log data stored in a database into LoggingEvents.
+ * <p>
+ * <b>NOTE:</b> This receiver cannot yet be created through Chainsaw's 
receiver panel.  
+ * It must be created through an XML configuration file.
+ * <p>
+ * This receiver supports database configuration via ConnectionSource, in the
+ * org.apache.log4j.db package: DriverManagerConnectionSource,
+ * DataSourceConnectionSource, JNDIConnectionSource
+ * <p>
+ * This database receiver differs from DBReceiver in that this receiver relies
+ * on custom SQL to retrieve logging event data, where DBReceiver requires the
+ * use of a log4j-defined schema.
+ * <p>
+ * A 'refreshMillis' int parameter controls SQL execution. If 'refreshMillis' 
is
+ * zero (the default), the receiver will run only one time. If it is set to any
+ * other numeric value, the SQL will be executed on a recurring basis every
+ * 'refreshMillis' milliseconds.
+ * <p>
+ * The receiver closes the connection and acquires a new connection on each 
+ * execution of the SQL (use pooled connections if possible).
+ * <p>
+ * If the SQL will be executing on a recurring basis, specify the IDField 
param -
+ * the column name holding the unique identifier (int) representing the logging
+ * event.
+ * <p>
+ * As events are retrieved, the column represented by IDField is examined and
+ * the largest value is held and used by the next execution of the SQL 
statement
+ * to avoid retrieving previously processed events.
+ * <p>
+ * As an example, the IDField references a 'COUNTER' (int, auto-increment,
+ * unique) column. The first execution of the SQL statement returns 500 rows,
+ * with a final value in the COUNTER field of 500.
+ * <p>
+ * The SQL statement is manipulated prior to the next execution, adding ' WHERE
+ * COUNTER > 500' to the statement to avoid retrieval of previously processed
+ * events.
+ * <p>
+ * The select statement must provide ALL fields which define a LoggingEvent.
+ * <p>
+ * The SQL statement MUST include the columns: LOGGER, TIMESTAMP, LEVEL, 
THREAD,
+ * MESSAGE, NDC, MDC, CLASS, METHOD, FILE, LINE, PROPERTIES, THROWABLE
+ * <p>
+ * Use ' AS ' in the SQL statement to alias the SQL's column names to match 
your
+ * database schema. (see example below).
+ * <p>
+ * Include all fields in the SQL statement, even if you don't have data for the
+ * field (specify an empty string as the value for columns which you don't have
+ * data).
+ * <p>
+ * The TIMESTAMP column must be a datetime.
+ * <p>
+ * Both a PROPERTIES column and an MDC column are supported. These fields
+ * represent Maps on the logging event, but require the use of string
+ * concatenation database functions to hold the (possibly multiple) name/value
+ * pairs in the column.
+ * <p>
+ * For example, to include both 'userid' and 'lastname' properties in the
+ * logging event (from either the PROPERTIES or MDC columns), the name/value
+ * pairs must be concatenated together by your database.
+ * <p>
+ * The resulting PROPERTIES or MDC column must have data in this format: 
{{name,
+ * value, name2, value2}}
+ * <p>
+ * The resulting PROPERTIES column would contain this text: {{userid, someone,
+ * lastname, mylastname}}
+ * <p>
+ * Here is an example of concatenating a PROPERTIES or MDC column using MySQL's
+ * concat function, where the 'application' and 'hostname' parameters were 
fixed
+ * text, but the 'log4jid' key's value is the value of the COUNTER column:
+ * <p>
+ * concat("{{application,databaselogs,hostname,mymachine,log4jid,", COUNTER,
+ * "}}") as PROPERTIES
+ * <p>
+ * log4jid is a special property that is used by Chainsaw to represent an 'ID'
+ * field. Specify this property to ensure you can map events in Chainsaw to
+ * events in the database if you need to go back and view events at a later 
time
+ * or save the events to XML for later analysis.
+ * <p>
+ * Here is a complete MySQL SQL statement which can be used to provide events 
to
+ * Chainsaw (note how in the example below, there is no column in logtable 
representing the throwable, so an
+ * empty string is passed in and an ALIAS is still defined):
+ * <p>
+ * select myloggercolumn as LOGGER, mytimestampcolumn as TIMESTAMP, 
mylevelcolumn as LEVEL, mythreadcolumn as
+ * THREAD, mymessagecolumn as MESSAGE, myndccolumn as NDC, mymdccolumn as MDC, 
myclasscolumn as CLASS, mymethodcolumn as
+ * METHOD, myfilecolumn as FILE, mylinecolumn as LINE,
+ * concat("{{application,databaselogs,hostname,mymachine, log4jid,",
+ * COUNTER,"}}") as PROPERTIES, "" as THROWABLE from logtable
+ * <p>
+ * @author Scott Deboy <[email protected]>
+ * <p>
+ */
+public class CustomSQLDBReceiver extends Receiver implements Pauseable, 
UnrecognizedElementHandler {
+
+    protected volatile Connection connection = null;
+
+    protected String sqlStatement = "";
+
+    /**
+     * By default we refresh data every 1000 milliseconds.
+     * 
+     * @see #setRefreshMillis
+     */
+    static int DEFAULT_REFRESH_MILLIS = 1000;
+
+    int refreshMillis = DEFAULT_REFRESH_MILLIS;
+
+    protected String idField = null;
+
+    int lastID = -1;
+
+    private static final String WHERE_CLAUSE = " WHERE ";
+
+    private static final String AND_CLAUSE = " AND ";
+
+    private boolean whereExists = false;
+
+    private boolean paused = false;
+
+    private ConnectionSource connectionSource;
+
+    public static final String LOG4J_ID_KEY = "log4jid";
+
+    private Job customReceiverJob;
+
+    public void activateOptions() {
+      
+      if(connectionSource == null)  {
+        throw new IllegalStateException(
+          "CustomSQLDBReceiver cannot function without a connection source");
+      }
+      whereExists = (sqlStatement.toUpperCase().indexOf(WHERE_CLAUSE) > -1);
+    
+      customReceiverJob = new CustomReceiverJob();
+        
+      if(this.repository == null) {
+        throw new IllegalStateException(
+        "CustomSQLDBReceiver cannot function without a reference to its owning 
repository");
+      }
+     
+    
+
+      if (repository instanceof LoggerRepositoryEx) {
+        Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+      
+        scheduler.schedule(
+          customReceiverJob, System.currentTimeMillis() + 500, refreshMillis);
+      }
+
+    }
+
+    void closeConnection() {
+        if (connection != null) {
+            try {
+                // LogLog.warn("closing the connection. ", new Exception("x"));
+                connection.close();
+            } catch (SQLException sqle) {
+                // nothing we can do here
+            }
+        }
+    }
+
+    public void setRefreshMillis(int refreshMillis) {
+        this.refreshMillis = refreshMillis;
+    }
+
+    public int getRefreshMillis() {
+        return refreshMillis;
+    }
+
+    /**
+     * @return Returns the connectionSource.
+     */
+    public ConnectionSource getConnectionSource() {
+        return connectionSource;
+    }
+
+    /**
+     * @param connectionSource
+     *            The connectionSource to set.
+     */
+    public void setConnectionSource(ConnectionSource connectionSource) {
+        this.connectionSource = connectionSource;
+    }
+
+    public void close() {
+        try {
+            if ((connection != null) && !connection.isClosed()) {
+                connection.close();
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        } finally {
+            connection = null;
+        }
+    }
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        close();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.log4j.plugins.Plugin#shutdown()
+     */
+    public void shutdown() {
+        getLogger().info("removing receiverJob from the Scheduler.");
+
+        if(this.repository instanceof LoggerRepositoryEx) {
+          Scheduler scheduler = ((LoggerRepositoryEx) 
repository).getScheduler();
+          scheduler.delete(customReceiverJob);
+        }
+
+        lastID = -1;
+    }
+
+    public void setSql(String s) {
+        sqlStatement = s;
+    }
+
+    public String getSql() {
+        return sqlStatement;
+    }
+
+    public void setIDField(String id) {
+        idField = id;
+    }
+
+    public String getIDField() {
+        return idField;
+    }
+
+    public synchronized void setPaused(boolean p) {
+        paused = p;
+    }
+
+    public synchronized boolean isPaused() {
+        return paused;
+    }
+
+    class CustomReceiverJob implements Job {
+        public void execute() {
+            int oldLastID = lastID;
+            try {
+                connection = connectionSource.getConnection();
+                Statement statement = connection.createStatement();
+
+                Logger eventLogger = null;
+                long timeStamp = 0L;
+                String level = null;
+                String threadName = null;
+                Object message = null;
+                String ndc = null;
+                Hashtable mdc = null;
+                String[] throwable = null;
+                String className = null;
+                String methodName = null;
+                String fileName = null;
+                String lineNumber = null;
+                Hashtable properties = null;
+
+                String currentSQLStatement = sqlStatement;
+                if (whereExists) {
+                    currentSQLStatement = sqlStatement + AND_CLAUSE + idField
+                            + " > " + lastID;
+                } else {
+                    currentSQLStatement = sqlStatement + WHERE_CLAUSE + idField
+                            + " > " + lastID;
+                }
+
+                ResultSet rs = statement.executeQuery(currentSQLStatement);
+
+                int i = 0;
+                while (rs.next()) {
+                    // add a small break every 1000 received events
+                    if (++i == 1000) {
+                        synchronized (this) {
+                            try {
+                                // add a delay
+                                wait(300);
+                            } catch (InterruptedException ie) {
+                            }
+                            i = 0;
+                        }
+                    }
+                    eventLogger = Logger.getLogger(rs.getString("LOGGER"));
+                    timeStamp = rs.getTimestamp("TIMESTAMP").getTime();
+
+                    level = rs.getString("LEVEL");
+                    threadName = rs.getString("THREAD");
+                    message = rs.getString("MESSAGE");
+                    ndc = rs.getString("NDC");
+
+                    String mdcString = rs.getString("MDC");
+                    mdc = new Hashtable();
+
+                    if (mdcString != null) {
+                        // support MDC being wrapped in {{name, value}}
+                        // or
+                        // just name, value
+                        if ((mdcString.indexOf("{{") > -1)
+                                && (mdcString.indexOf("}}") > -1)) {
+                            mdcString = mdcString
+                                    .substring(mdcString.indexOf("{{") + 2,
+                                            mdcString.indexOf("}}"));
+                        }
+
+                        StringTokenizer tok = new StringTokenizer(mdcString,
+                                ",");
+
+                        while (tok.countTokens() > 1) {
+                            mdc.put(tok.nextToken(), tok.nextToken());
+                        }
+                    }
+
+                    throwable = new String[] { rs.getString("THROWABLE") };
+                    className = rs.getString("CLASS");
+                    methodName = rs.getString("METHOD");
+                    fileName = rs.getString("FILE");
+                    lineNumber = rs.getString("LINE");
+
+                    // if properties are provided in the
+                    // SQL they can be used here (for example, to route
+                    // events to a unique tab in
+                    // Chainsaw if the machinename and/or appname
+                    // property
+                    // are set)
+                    String propertiesString = rs.getString("PROPERTIES");
+                    properties = new Hashtable();
+
+                    if (propertiesString != null) {
+                        // support properties being wrapped in {{name,
+                        // value}} or just name, value
+                        if ((propertiesString.indexOf("{{") > -1)
+                                && (propertiesString.indexOf("}}") > -1)) {
+                            propertiesString = propertiesString.substring(
+                                    propertiesString.indexOf("{{") + 2,
+                                    propertiesString.indexOf("}}"));
+                        }
+
+                        StringTokenizer tok2 = new StringTokenizer(
+                                propertiesString, ",");
+                        while (tok2.countTokens() > 1) {
+                            String tokenName = tok2.nextToken();
+                            String value = tok2.nextToken();
+                            if (tokenName.equals(LOG4J_ID_KEY)) {
+                                try {
+                                    int thisInt = Integer.parseInt(value);
+                                    value = String.valueOf(thisInt);
+                                    if (thisInt > lastID) {
+                                        lastID = thisInt;
+                                    }
+                                } catch (Exception e) {
+                                }
+                            }
+                            properties.put(tokenName, value);
+                        }
+                    }
+
+                    Level levelImpl = Level.toLevel(level);
+
+
+                                       LocationInfo locationInfo = new 
LocationInfo(fileName,
+                                   className, methodName, lineNumber);
+       
+                                       ThrowableInformation throwableInfo =  
new ThrowableInformation(
+                                           throwable);
+       
+                                       properties.putAll(mdc);
+               
+                                   LoggingEvent event = new 
LoggingEvent(eventLogger.getName(),
+                                           eventLogger, timeStamp, levelImpl, 
message,
+                                           threadName,
+                                           throwableInfo,
+                                           ndc,
+                                           locationInfo,
+                                           properties);
+
+                    doPost(event);
+                }
+                //log when rows are retrieved
+                if (lastID != oldLastID) {
+                    getLogger().debug("lastID: " + lastID);
+                    oldLastID = lastID;
+                }
+
+                statement.close();
+                statement = null;
+            } catch (SQLException sqle) {
+                getLogger()
+                        .error("*************Problem receiving events", sqle);
+            } finally {
+                closeConnection();
+            }
+
+            // if paused, loop prior to executing sql query
+            synchronized (this) {
+                while (isPaused()) {
+                    try {
+                        wait(1000);
+                    } catch (InterruptedException ie) {
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @{inheritDoc}
+     */
+  public boolean parseUnrecognizedElement(Element element, Properties props) 
throws Exception {
+        if ("connectionSource".equals(element.getNodeName())) {
+            Object instance =
+                    DOMConfigurator.parseElement(element, props, 
ConnectionSource.class);
+            if (instance instanceof ConnectionSource) {
+               ConnectionSource source = (ConnectionSource) instance;
+               source.activateOptions();
+               setConnectionSource(source);
+            }
+            return true;
+        }
+        return false;
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/db/DBAppender.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/db/DBAppender.java 
b/src/main/java/org/apache/log4j/db/DBAppender.java
new file mode 100644
index 0000000..15deac3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DBAppender.java
@@ -0,0 +1,403 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.db.dialect.SQLDialect;
+import org.apache.log4j.db.dialect.Util;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+
+
+/**
+ * The DBAppender inserts loggin events into three database tables in a format
+ * independent of the Java programming language. The three tables that
+ * DBAppender inserts to must exists before DBAppender can be used. These 
tables
+ * may be created with the help of SQL scripts found in the
+ * <em>src/java/org/apache/log4j/db/dialect</em> directory. There is a
+ * specific script for each of the most popular database systems. If the script
+ * for your particular type of database system is missing, it should be quite
+ * easy to write one, taking example on the already existing scripts. If you
+ * send them to us, we will gladly include missing scripts in future releases.
+ *
+ * <p>
+ * If the JDBC driver you are using supports the
+ * {@link java.sql.Statement#getGeneratedKeys}method introduced in JDBC 3.0
+ * specification, then you are all set. Otherwise, there must be an
+ * {@link SQLDialect}appropriate for your database system. Currently, we have
+ * dialects for PostgreSQL, MySQL, Oracle and MsSQL. As mentioed previously, an
+ * SQLDialect is required only if the JDBC driver for your database system does
+ * not support the {@link java.sql.Statement#getGeneratedKeys getGeneratedKeys}
+ * method.
+ * </p>
+ *
+ * <table border="1" cellpadding="4">
+ * <tr>
+ * <th>RDBMS</th>
+ * <th>supports <br/><code>getGeneratedKeys()</code> method</th>
+ * <th>specific <br/>SQLDialect support</th>
+ * <tr>
+ * <tr>
+ * <td>PostgreSQL</td>
+ * <td align="center">NO</td>
+ * <td>present and used</td>
+ * <tr>
+ * <tr>
+ * <td>MySQL</td>
+ * <td align="center">YES</td>
+ * <td>present, but not actually needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>Oracle</td>
+ * <td align="center">YES</td>
+ * <td>present, but not actually needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>DB2</td>
+ * <td align="center">YES</td>
+ * <td>not present, and not needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>MsSQL</td>
+ * <td align="center">YES</td>
+ * <td>not present, and not needed or used</td>
+ * <tr>
+ * <tr>
+ *   <td>HSQL</td>
+ *    <td align="center">NO</td>
+ *    <td>present and used</td>
+ * <tr>
+ *
+ * </table>
+ * <p>
+ * <b>Performance: </b> Experiments show that writing a single event into the
+ * database takes approximately 50 milliseconds, on a "standard" PC. If pooled
+ * connections are used, this figure drops to under 10 milliseconds. Note that
+ * most JDBC drivers already ship with connection pooling support.
+ * </p>
+ *
+ *
+ *
+ * <p>
+ * <b>Configuration </b> DBAppender can be configured programmatically, or 
using
+ * {@link org.apache.log4j.xml.DOMConfigurator JoranConfigurator}. Example
+ * scripts can be found in the <em>tests/input/db</em> directory.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Ray DeCampo
+ */
+public class DBAppender extends AppenderSkeleton implements 
UnrecognizedElementHandler {
+  static final String insertPropertiesSQL =
+    "INSERT INTO  logging_event_property (event_id, mapped_key, mapped_value) 
VALUES (?, ?, ?)";
+  static final String insertExceptionSQL =
+    "INSERT INTO  logging_event_exception (event_id, i, trace_line) VALUES (?, 
?, ?)";
+  static final String insertSQL;
+  private static final Method GET_GENERATED_KEYS_METHOD;
+
+
+  static {
+    StringBuffer sql = new StringBuffer();
+    sql.append("INSERT INTO logging_event (");
+    sql.append("sequence_number, ");
+    sql.append("timestamp, ");
+    sql.append("rendered_message, ");
+    sql.append("logger_name, ");
+    sql.append("level_string, ");
+    sql.append("ndc, ");
+    sql.append("thread_name, ");
+    sql.append("reference_flag, ");
+    sql.append("caller_filename, ");
+    sql.append("caller_class, ");
+    sql.append("caller_method, ");
+    sql.append("caller_line) ");
+    sql.append(" VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?)");
+    insertSQL = sql.toString();
+    //
+    //   PreparedStatement.getGeneratedKeys added in JDK 1.4
+    //
+    Method getGeneratedKeysMethod;
+    try {
+        getGeneratedKeysMethod = 
PreparedStatement.class.getMethod("getGeneratedKeys", null);
+    } catch(Exception ex) {
+        getGeneratedKeysMethod = null;
+    }
+    GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
+  }
+
+  ConnectionSource connectionSource;
+  boolean cnxSupportsGetGeneratedKeys = false;
+  boolean cnxSupportsBatchUpdates = false;
+  SQLDialect sqlDialect;
+  boolean locationInfo = false;
+  
+
+  public DBAppender() {
+      super(false);
+  }
+
+  public void activateOptions() {
+    LogLog.debug("DBAppender.activateOptions called");
+
+    if (connectionSource == null) {
+      throw new IllegalStateException(
+        "DBAppender cannot function without a connection source");
+    }
+
+    sqlDialect = Util.getDialectFromCode(connectionSource.getSQLDialectCode());
+    if (GET_GENERATED_KEYS_METHOD != null) {
+        cnxSupportsGetGeneratedKeys = 
connectionSource.supportsGetGeneratedKeys();
+    } else {
+        cnxSupportsGetGeneratedKeys = false;
+    }
+    cnxSupportsBatchUpdates = connectionSource.supportsBatchUpdates();
+    if (!cnxSupportsGetGeneratedKeys && (sqlDialect == null)) {
+      throw new IllegalStateException(
+        "DBAppender cannot function if the JDBC driver does not support 
getGeneratedKeys method *and* without a specific SQL dialect");
+    }
+    
+    // all nice and dandy on the eastern front
+    super.activateOptions();
+  }
+
+  /**
+   * @return Returns the connectionSource.
+   */
+  public ConnectionSource getConnectionSource() {
+    return connectionSource;
+  }
+
+  /**
+   * @param connectionSource
+   *          The connectionSource to set.
+   */
+  public void setConnectionSource(ConnectionSource connectionSource) {
+    LogLog.debug("setConnectionSource called for DBAppender");
+    this.connectionSource = connectionSource;
+  }
+
+  protected void append(LoggingEvent event) {
+      Connection connection = null;
+      try {
+          connection = connectionSource.getConnection();
+          connection.setAutoCommit(false);
+          
+          PreparedStatement insertStatement;
+          if (cnxSupportsGetGeneratedKeys) {
+                 insertStatement = connection.prepareStatement(insertSQL, 
Statement.RETURN_GENERATED_KEYS);
+          } else {
+              insertStatement = connection.prepareStatement(insertSQL);
+          }
+
+/*          insertStatement.setLong(1, event.getSequenceNumber());*/
+                 insertStatement.setLong(1, 0);
+               
+          insertStatement.setLong(2, event.getTimeStamp());
+          insertStatement.setString(3, event.getRenderedMessage());
+          insertStatement.setString(4, event.getLoggerName());
+          insertStatement.setString(5, event.getLevel().toString());
+          insertStatement.setString(6, event.getNDC());
+          insertStatement.setString(7, event.getThreadName());
+          insertStatement.setShort(8, DBHelper.computeReferenceMask(event));
+          
+          LocationInfo li;
+          
+          if (event.locationInformationExists() || locationInfo) {
+              li = event.getLocationInformation();
+          } else {
+              li = LocationInfo.NA_LOCATION_INFO;
+          }
+          
+          insertStatement.setString(9, li.getFileName());
+          insertStatement.setString(10, li.getClassName());
+          insertStatement.setString(11, li.getMethodName());
+          insertStatement.setString(12, li.getLineNumber());
+          
+          int updateCount = insertStatement.executeUpdate();
+          if (updateCount != 1) {
+              LogLog.warn("Failed to insert loggingEvent");
+          }
+          
+          ResultSet rs = null;
+          Statement idStatement = null;
+          boolean gotGeneratedKeys = false;
+          if (cnxSupportsGetGeneratedKeys) {
+              try {
+                  rs = (ResultSet) 
GET_GENERATED_KEYS_METHOD.invoke(insertStatement, null);
+                  gotGeneratedKeys = true;
+              } catch(InvocationTargetException ex) {
+                  Throwable target = ex.getTargetException();
+                  if (target instanceof SQLException) {
+                      throw (SQLException) target;
+                  }
+                  throw ex; 
+              } catch(IllegalAccessException ex) {
+                  LogLog.warn("IllegalAccessException invoking 
PreparedStatement.getGeneratedKeys", ex);
+              }
+          }
+          
+          if (!gotGeneratedKeys) {
+              insertStatement.close();
+              insertStatement = null;
+              
+              idStatement = connection.createStatement();
+              idStatement.setMaxRows(1);
+              rs = idStatement.executeQuery(sqlDialect.getSelectInsertId());
+          }
+          
+          // A ResultSet cursor is initially positioned before the first row; 
the 
+          // first call to the method next makes the first row the current row
+          rs.next();
+          int eventId = rs.getInt(1);
+          
+          rs.close();
+
+          // we no longer need the insertStatement
+          if(insertStatement != null) {
+              insertStatement.close();
+              insertStatement = null;
+          }
+
+          if(idStatement != null) {
+              idStatement.close();
+              idStatement = null;
+          }
+
+          Set propertiesKeys = event.getPropertyKeySet();
+          
+          if (propertiesKeys.size() > 0) {
+              PreparedStatement insertPropertiesStatement =
+                  connection.prepareStatement(insertPropertiesSQL);
+              
+              for (Iterator i = propertiesKeys.iterator(); i.hasNext();) {
+                  String key = (String) i.next();
+                  String value = (String) event.getProperty(key);
+                  
+                  //LogLog.info("id " + eventId + ", key " + key + ", value " 
+ value);
+                  insertPropertiesStatement.setInt(1, eventId);
+                  insertPropertiesStatement.setString(2, key);
+                  insertPropertiesStatement.setString(3, value);
+                  
+                  if (cnxSupportsBatchUpdates) {
+                      insertPropertiesStatement.addBatch();
+                  } else {
+                      insertPropertiesStatement.execute();
+                  }
+              }
+              
+              if (cnxSupportsBatchUpdates) {
+                  insertPropertiesStatement.executeBatch();
+              }
+              
+              insertPropertiesStatement.close();
+              insertPropertiesStatement = null;
+          }
+          
+          String[] strRep = event.getThrowableStrRep();
+          
+          if (strRep != null) {
+              LogLog.debug("Logging an exception");
+              
+              PreparedStatement insertExceptionStatement =
+                  connection.prepareStatement(insertExceptionSQL);
+              
+              for (short i = 0; i < strRep.length; i++) {
+                  insertExceptionStatement.setInt(1, eventId);
+                  insertExceptionStatement.setShort(2, i);
+                  insertExceptionStatement.setString(3, strRep[i]);
+                  if (cnxSupportsBatchUpdates) {
+                      insertExceptionStatement.addBatch();
+                  } else {
+                      insertExceptionStatement.execute();
+                  }
+              }
+              if (cnxSupportsBatchUpdates) {
+                  insertExceptionStatement.executeBatch();
+              }
+              insertExceptionStatement.close();
+              insertExceptionStatement = null;
+          }
+          
+          connection.commit();
+      } catch (Throwable sqle) {
+          LogLog.error("problem appending event", sqle);
+      } finally {
+          DBHelper.closeConnection(connection);
+      }
+  }
+
+  public void close() {
+    closed = true;
+  }
+
+  /**
+   * Returns value of the <b>LocationInfo </b> property which determines 
whether
+   * caller's location info is written to the database.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  /**
+   * If true, the information written to the database will include caller's
+   * location information. Due to performance concerns, by default no location
+   * information is written to the database.
+   */
+  public void setLocationInfo(boolean locationInfo) {
+    this.locationInfo = locationInfo;
+  }
+
+    /**
+     * Gets whether appender requires a layout.
+     * @return false
+     */
+  public boolean requiresLayout() {
+      return false;
+  }
+
+    /**
+     * @{inheritDoc}
+     */
+  public boolean parseUnrecognizedElement(Element element, Properties props) 
throws Exception {
+        if ("connectionSource".equals(element.getNodeName())) {
+            Object instance =
+                    DOMConfigurator.parseElement(element, props, 
ConnectionSource.class);
+            if (instance instanceof ConnectionSource) {
+               ConnectionSource source = (ConnectionSource) instance;
+               source.activateOptions();
+               setConnectionSource(source);
+            }
+            return true;
+        }
+        return false;
+  }
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/db/DBHelper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/db/DBHelper.java 
b/src/main/java/org/apache/log4j/db/DBHelper.java
new file mode 100644
index 0000000..b400506
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DBHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Set;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * @author Ceki G&uuml;lc&uuml;
+ *
+ */
+public class DBHelper {
+  
+  public final static short PROPERTIES_EXIST = 0x01;
+  public final static short EXCEPTION_EXISTS = 0x02;
+  
+  public  static short computeReferenceMask(LoggingEvent event) {
+    short mask = 0;
+    Set propertiesKeys = event.getPropertyKeySet();
+    if(propertiesKeys.size() > 0) {
+      mask = PROPERTIES_EXIST;
+    }
+    String[] strRep = event.getThrowableStrRep();
+    if(strRep != null) {
+      mask |= EXCEPTION_EXISTS;
+    }
+    return mask;
+  }
+  
+  static public void closeConnection(Connection connection) {
+    if(connection != null) {
+      try { 
+        connection.close();
+      } catch(SQLException sqle) {
+        // static utility classes should not log without an explicit repository
+        // reference
+      }
+    }
+  }
+  
+  public static void closeStatement(Statement statement) {
+    if(statement != null) {
+      try {
+        statement.close();
+      } catch(SQLException sqle) {
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/db/DBReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/db/DBReceiver.java 
b/src/main/java/org/apache/log4j/db/DBReceiver.java
new file mode 100644
index 0000000..eee1068
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DBReceiver.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.scheduler.Scheduler;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+/**
+ *
+ * @author Scott Deboy <[email protected]>
+ * @author Ceki G&uuml;lc&uuml;
+ *
+ */
+public class DBReceiver extends Receiver implements Pauseable, 
UnrecognizedElementHandler {
+  /**
+   * By default we refresh data every 1000 milliseconds.
+   * @see #setRefreshMillis
+   */
+  static int DEFAULT_REFRESH_MILLIS = 1000;
+  ConnectionSource connectionSource;
+  int refreshMillis = DEFAULT_REFRESH_MILLIS;
+  DBReceiverJob receiverJob;
+  boolean paused = false;
+
+  public void activateOptions() {
+    
+    if(connectionSource == null)  {
+      throw new IllegalStateException(
+        "DBAppender cannot function without a connection source");
+    }
+  
+    receiverJob = new DBReceiverJob(this);
+    receiverJob.setLoggerRepository(repository);
+      
+    if(this.repository == null) {
+      throw new IllegalStateException(
+      "DBAppender cannot function without a reference to its owning 
repository");
+    }
+
+    if (repository instanceof LoggerRepositoryEx) {
+        Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+    
+        scheduler.schedule(
+            receiverJob, System.currentTimeMillis() + 500, refreshMillis);
+    }
+   
+  }
+
+  public void setRefreshMillis(int refreshMillis) {
+    this.refreshMillis = refreshMillis;
+  }
+
+  public int getRefreshMillis() {
+    return refreshMillis;
+  }
+
+
+  /**
+   * @return Returns the connectionSource.
+   */
+  public ConnectionSource getConnectionSource() {
+    return connectionSource;
+  }
+
+
+  /**
+   * @param connectionSource The connectionSource to set.
+   */
+  public void setConnectionSource(ConnectionSource connectionSource) {
+    this.connectionSource = connectionSource;
+  }
+
+
+  /* (non-Javadoc)
+   * @see org.apache.log4j.plugins.Plugin#shutdown()
+   */
+  public void shutdown() {
+    getLogger().info("removing receiverJob from the Scheduler.");
+
+    if(this.repository instanceof LoggerRepositoryEx) {
+      Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+      scheduler.delete(receiverJob);
+    }
+  }
+
+
+  /* (non-Javadoc)
+   * @see org.apache.log4j.plugins.Pauseable#setPaused(boolean)
+   */
+  public void setPaused(boolean paused) {
+    this.paused = paused;
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.log4j.plugins.Pauseable#isPaused()
+   */
+  public boolean isPaused() {
+    return paused;
+  }
+
+    /**
+     * @{inheritDoc}
+     */
+  public boolean parseUnrecognizedElement(Element element, Properties props) 
throws Exception {
+        if ("connectionSource".equals(element.getNodeName())) {
+            Object instance =
+                    DOMConfigurator.parseElement(element, props, 
ConnectionSource.class);
+            if (instance instanceof ConnectionSource) {
+                ConnectionSource source = (ConnectionSource) instance;
+                source.activateOptions();
+                setConnectionSource(source);
+            }
+            return true;
+        }
+        return false;
+  }
+
+}

Reply via email to