Properly track resources during shutdown. 

Detect if an a problem occurred during shutdown.

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/78fe32c0
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/78fe32c0
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/78fe32c0

Branch: refs/heads/LOG4J2-1349-gcfree-threadcontext
Commit: 78fe32c0d4643e6692bbcdc3ed754be90f647183
Parents: 97e45ed
Author: ggregory <ggreg...@us-l-gg02.rocketsoftware.com>
Authored: Wed Sep 7 13:37:42 2016 -0400
Committer: ggregory <ggreg...@us-l-gg02.rocketsoftware.com>
Committed: Wed Sep 7 13:37:42 2016 -0400

----------------------------------------------------------------------
 .../log4j/core/appender/AbstractManager.java    |   11 +-
 .../appender/AbstractOutputStreamAppender.java  |    2 +-
 .../core/appender/AbstractWriterAppender.java   |  252 +--
 .../core/appender/OutputStreamManager.java      |    4 +-
 .../core/appender/RollingFileAppender.java      |  748 ++++----
 .../log4j/core/appender/WriterManager.java      |  301 ++--
 .../appender/db/AbstractDatabaseAppender.java   |  284 +--
 .../appender/db/AbstractDatabaseManager.java    |  458 ++---
 .../appender/db/jdbc/JdbcDatabaseManager.java   |  515 +++---
 .../appender/db/jpa/JpaDatabaseManager.java     |  381 ++--
 .../log4j/core/appender/mom/JmsAppender.java    |  430 ++---
 .../log4j/core/appender/mom/JmsManager.java     |  351 ++--
 .../appender/mom/jeromq/JeroMqAppender.java     |  370 ++--
 .../core/appender/mom/jeromq/JeroMqManager.java |  443 ++---
 .../core/appender/mom/kafka/KafkaAppender.java  |  240 +--
 .../core/appender/mom/kafka/KafkaManager.java   |  159 +-
 .../appender/rolling/RollingFileManager.java    |  958 +++++-----
 .../logging/log4j/core/net/JndiManager.java     |  288 +--
 .../log4j/core/net/server/JmsServer.java        |  295 +--
 .../logging/log4j/core/util/JndiCloser.java     |  118 +-
 .../log4j/flume/appender/FlumeAppender.java     |    6 +-
 .../log4j/flume/appender/FlumeAvroManager.java  |  670 +++----
 .../flume/appender/FlumeEmbeddedManager.java    |  555 +++---
 .../flume/appender/FlumePersistentManager.java  | 1699 +++++++++---------
 .../nosql/appender/NoSqlDatabaseManager.java    |  440 ++---
 25 files changed, 5008 insertions(+), 4970 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
index acd2759..a08ec75 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
@@ -74,18 +74,21 @@ public abstract class AbstractManager implements 
AutoCloseable {
         stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, 
AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
     }
 
-    public void stop(final long timeout, final TimeUnit timeUnit) {
+    public boolean stop(final long timeout, final TimeUnit timeUnit) {
+        boolean stopped = true;
         LOCK.lock();
         try {
             --count;
             if (count <= 0) {
                 MAP.remove(name);
                 LOGGER.debug("Shutting down {} {}", 
this.getClass().getSimpleName(), getName());
-                releaseSub(timeout, timeUnit);
+                stopped = releaseSub(timeout, timeUnit);
+                LOGGER.debug("Shut down {} {}, all resources released: {}", 
this.getClass().getSimpleName(), getName(), stopped);
             }
         } finally {
             LOCK.unlock();
         }
+        return stopped;
     }
 
     /**
@@ -143,9 +146,11 @@ public abstract class AbstractManager implements 
AutoCloseable {
      * lock is held. A timeout is passed for implementors to use as they see 
fit.
      * @param timeout timeout
      * @param timeUnit timeout time unit
+     * @return true if all resources were closed normally, false otherwise.
      */
-    protected void releaseSub(final long timeout, final TimeUnit timeUnit) {
+    protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
         // This default implementation does nothing.
+        return true;
     }
 
     protected int getCount() {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
index d4ad3f7..f7e86b4 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
@@ -139,7 +139,7 @@ public abstract class AbstractOutputStreamAppender<M 
extends OutputStreamManager
     @Override
     protected boolean stop(final long timeout, final TimeUnit timeUnit, final 
boolean changeLifeCycleState) {
         boolean stopped = super.stop(timeout, timeUnit, changeLifeCycleState);
-        manager.stop(timeout, timeUnit);
+        stopped &= manager.stop(timeout, timeUnit);
         if (changeLifeCycleState) {
             setStopped();
         }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
index 7e1d46b..17a0bb4 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
@@ -1,126 +1,126 @@
-/*
- * 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.logging.log4j.core.appender;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.StringLayout;
-
-/**
- * Appends log events as strings to a writer.
- * 
- * @param <M>
- *            The kind of {@link WriterManager} under management
- */
-public abstract class AbstractWriterAppender<M extends WriterManager> extends 
AbstractAppender {
-
-    /**
-     * Immediate flush means that the underlying writer will be flushed at the
-     * end of each append operation. Immediate flush is slower but ensures that
-     * each append request is actually written. If <code>immediateFlush</code>
-     * is set to {@code false}, then there is a good chance that the last few
-     * logs events are not actually written to persistent media if and when the
-     * application crashes.
-     */
-    protected final boolean immediateFlush;
-    private final M manager;
-    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
-    private final Lock readLock = readWriteLock.readLock();
-
-    /**
-     * Instantiates.
-     * 
-     * @param name
-     *            The name of the Appender.
-     * @param layout
-     *            The layout to format the message.
-     * @param manager
-     *            The OutputStreamManager.
-     */
-    protected AbstractWriterAppender(final String name, final StringLayout 
layout, final Filter filter,
-            final boolean ignoreExceptions, final boolean immediateFlush, 
final M manager) {
-        super(name, filter, layout, ignoreExceptions);
-        this.manager = manager;
-        this.immediateFlush = immediateFlush;
-    }
-
-    /**
-     * Actual writing occurs here.
-     * <p>
-     * Most subclasses will need to override this method.
-     * </p>
-     * 
-     * @param event
-     *            The LogEvent.
-     */
-    @Override
-    public void append(final LogEvent event) {
-        readLock.lock();
-        try {
-            final String str = getStringLayout().toSerializable(event);
-            if (str.length() > 0) {
-                manager.write(str);
-                if (this.immediateFlush || event.isEndOfBatch()) {
-                    manager.flush();
-                }
-            }
-        } catch (final AppenderLoggingException ex) {
-            error("Unable to write " + manager.getName() + " for appender " + 
getName() + ": " + ex);
-            throw ex;
-        } finally {
-            readLock.unlock();
-        }
-    }
-
-    /**
-     * Gets the manager.
-     * 
-     * @return the manager.
-     */
-    public M getManager() {
-        return manager;
-    }
-
-    public StringLayout getStringLayout() {
-        return (StringLayout) getLayout();
-    }
-
-    @Override
-    public void start() {
-        if (getLayout() == null) {
-            LOGGER.error("No layout set for the appender named [{}].", 
getName());
-        }
-        if (manager == null) {
-            LOGGER.error("No OutputStreamManager set for the appender named 
[{}].", getName());
-        }
-        super.start();
-    }
-
-    @Override
-    public boolean stop(final long timeout, final TimeUnit timeUnit) {
-        setStopping();
-        super.stop(timeout, timeUnit, false);
-        manager.stop(timeout, timeUnit);
-        setStopped();
-        return true;
-    }
-}
+/*
+ * 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.logging.log4j.core.appender;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.StringLayout;
+
+/**
+ * Appends log events as strings to a writer.
+ * 
+ * @param <M>
+ *            The kind of {@link WriterManager} under management
+ */
+public abstract class AbstractWriterAppender<M extends WriterManager> extends 
AbstractAppender {
+
+    /**
+     * Immediate flush means that the underlying writer will be flushed at the
+     * end of each append operation. Immediate flush is slower but ensures that
+     * each append request is actually written. If <code>immediateFlush</code>
+     * is set to {@code false}, then there is a good chance that the last few
+     * logs events are not actually written to persistent media if and when the
+     * application crashes.
+     */
+    protected final boolean immediateFlush;
+    private final M manager;
+    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+    private final Lock readLock = readWriteLock.readLock();
+
+    /**
+     * Instantiates.
+     * 
+     * @param name
+     *            The name of the Appender.
+     * @param layout
+     *            The layout to format the message.
+     * @param manager
+     *            The OutputStreamManager.
+     */
+    protected AbstractWriterAppender(final String name, final StringLayout 
layout, final Filter filter,
+            final boolean ignoreExceptions, final boolean immediateFlush, 
final M manager) {
+        super(name, filter, layout, ignoreExceptions);
+        this.manager = manager;
+        this.immediateFlush = immediateFlush;
+    }
+
+    /**
+     * Actual writing occurs here.
+     * <p>
+     * Most subclasses will need to override this method.
+     * </p>
+     * 
+     * @param event
+     *            The LogEvent.
+     */
+    @Override
+    public void append(final LogEvent event) {
+        readLock.lock();
+        try {
+            final String str = getStringLayout().toSerializable(event);
+            if (str.length() > 0) {
+                manager.write(str);
+                if (this.immediateFlush || event.isEndOfBatch()) {
+                    manager.flush();
+                }
+            }
+        } catch (final AppenderLoggingException ex) {
+            error("Unable to write " + manager.getName() + " for appender " + 
getName() + ": " + ex);
+            throw ex;
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Gets the manager.
+     * 
+     * @return the manager.
+     */
+    public M getManager() {
+        return manager;
+    }
+
+    public StringLayout getStringLayout() {
+        return (StringLayout) getLayout();
+    }
+
+    @Override
+    public void start() {
+        if (getLayout() == null) {
+            LOGGER.error("No layout set for the appender named [{}].", 
getName());
+        }
+        if (manager == null) {
+            LOGGER.error("No OutputStreamManager set for the appender named 
[{}].", getName());
+        }
+        super.start();
+    }
+
+    @Override
+    public boolean stop(final long timeout, final TimeUnit timeUnit) {
+        setStopping();
+        boolean stopped = super.stop(timeout, timeUnit, false);
+        stopped &= manager.stop(timeout, timeUnit);
+        setStopped();
+        return stopped;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
index c5b7a45..d7a2627 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
@@ -131,9 +131,9 @@ public class OutputStreamManager extends AbstractManager 
implements ByteBufferDe
      * Default hook to write footer during close.
      */
     @Override
-    public void releaseSub(final long timeout, final TimeUnit timeUnit) {
+    public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
         writeFooter();
-        closeOutputStream();
+        return closeOutputStream();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
index 726c2d2..b799d1c 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
@@ -1,374 +1,374 @@
-/*
- * 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.logging.log4j.core.appender;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.Deflater;
-
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
-import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
-import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
-import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.net.Advertiser;
-import org.apache.logging.log4j.core.util.Booleans;
-import org.apache.logging.log4j.core.util.Integers;
-
-/**
- * An appender that writes to files and can roll over at intervals.
- */
-@Plugin(name = "RollingFile", category = "Core", elementType = "appender", 
printObject = true)
-public final class RollingFileAppender extends 
AbstractOutputStreamAppender<RollingFileManager> {
-
-    /**
-     * Builds FileAppender instances.
-     * 
-     * @param <B>
-     *            This builder class
-     */
-    public static class Builder<B extends Builder<B>> extends 
AbstractOutputStreamAppender.Builder<B>
-            implements 
org.apache.logging.log4j.core.util.Builder<RollingFileAppender> {
-
-        @PluginBuilderAttribute
-        @Required
-        private String fileName;
-
-        @PluginBuilderAttribute
-        @Required
-        private String filePattern;
-
-        @PluginBuilderAttribute
-        private boolean append = true;
-
-        @PluginBuilderAttribute
-        private boolean locking;
-
-        @PluginElement("Policy") 
-        @Required
-        private TriggeringPolicy policy;
-        
-        @PluginElement("Strategy") 
-        private RolloverStrategy strategy;
-
-        @PluginBuilderAttribute
-        private boolean advertise;
-
-        @PluginBuilderAttribute
-        private String advertiseUri;
-
-        @PluginBuilderAttribute
-        private boolean createOnDemand;
-
-        @PluginConfiguration
-        private Configuration configuration;
-
-        @Override
-        public RollingFileAppender build() {
-            // Even though some variables may be annotated with @Required, we 
must still perform validation here for
-            // call sites that build builders programmatically.
-            final boolean isBufferedIo = isBufferedIo();
-            final int bufferSize = getBufferSize();
-            if (getName() == null) {
-                LOGGER.error("RollingFileAppender '{}': No name provided.", 
getName());
-                return null;
-            }
-
-            if (!isBufferedIo && bufferSize > 0) {
-                LOGGER.warn("RollingFileAppender '{}': The bufferSize is set 
to {} but bufferedIO is not true", getName(), bufferSize);
-            }
-
-            if (fileName == null) {
-                LOGGER.error("RollingFileAppender '{}': No file name 
provided.", getName());
-                return null;
-            }
-
-            if (filePattern == null) {
-                LOGGER.error("RollingFileAppender '{}': No file name pattern 
provided.", getName());
-                return null;
-            }
-
-            if (policy == null) {
-                LOGGER.error("RollingFileAppender '{}': No TriggeringPolicy 
provided.", getName());
-                return null;
-            }
-
-            if (strategy == null) {
-                strategy = DefaultRolloverStrategy.createStrategy(null, null, 
null,
-                        String.valueOf(Deflater.DEFAULT_COMPRESSION), null, 
true, configuration);
-            }
-
-            if (strategy == null) {
-                strategy = DefaultRolloverStrategy.createStrategy(null, null, 
null,
-                        String.valueOf(Deflater.DEFAULT_COMPRESSION), null, 
true, configuration);
-            }
-
-            final RollingFileManager manager = 
RollingFileManager.getFileManager(fileName, filePattern, append,
-                    isBufferedIo, policy, strategy, advertiseUri, getLayout(), 
bufferSize, isImmediateFlush(),
-                    createOnDemand, configuration);
-            if (manager == null) {
-                return null;
-            }
-
-            manager.initialize();
-
-            return new RollingFileAppender(getName(), getLayout(), 
getFilter(), manager, fileName, filePattern,
-                    isIgnoreExceptions(), isImmediateFlush(), advertise ? 
configuration.getAdvertiser() : null);
-        }
-
-        public String getAdvertiseUri() {
-            return advertiseUri;
-        }
-
-        public Configuration getConfiguration() {
-            return configuration;
-        }
-
-        public String getFileName() {
-            return fileName;
-        }
-
-        public boolean isAdvertise() {
-            return advertise;
-        }
-
-        public boolean isAppend() {
-            return append;
-        }
-
-        public boolean isCreateOnDemand() {
-            return createOnDemand;
-        }
-
-        public boolean isLocking() {
-            return locking;
-        }
-
-        public B withAdvertise(final boolean advertise) {
-            this.advertise = advertise;
-            return asBuilder();
-        }
-
-        public B withAdvertiseUri(final String advertiseUri) {
-            this.advertiseUri = advertiseUri;
-            return asBuilder();
-        }
-
-        public B withAppend(final boolean append) {
-            this.append = append;
-            return asBuilder();
-        }
-
-        public B withConfiguration(final Configuration config) {
-            this.configuration = config;
-            return asBuilder();
-        }
-
-        public B withFileName(final String fileName) {
-            this.fileName = fileName;
-            return asBuilder();
-        }
-
-        public B withCreateOnDemand(final boolean createOnDemand) {
-            this.createOnDemand = createOnDemand;
-            return asBuilder();
-        }
-
-        public B withLocking(final boolean locking) {
-            this.locking = locking;
-            return asBuilder();
-        }
-
-        public String getFilePattern() {
-            return filePattern;
-        }
-
-        public TriggeringPolicy getPolicy() {
-            return policy;
-        }
-
-        public RolloverStrategy getStrategy() {
-            return strategy;
-        }
-
-        public B withFilePattern(final String filePattern) {
-            this.filePattern = filePattern;
-            return asBuilder();
-        }
-
-        public B withPolicy(final TriggeringPolicy policy) {
-            this.policy = policy;
-            return asBuilder();
-        }
-
-        public B withStrategy(final RolloverStrategy strategy) {
-            this.strategy = strategy;
-            return asBuilder();
-        }
-
-    }
-    
-    private static final int DEFAULT_BUFFER_SIZE = 8192;
-
-    private final String fileName;
-    private final String filePattern;
-    private Object advertisement;
-    private final Advertiser advertiser;
-
-    private RollingFileAppender(final String name, final Layout<? extends 
Serializable> layout, final Filter filter,
-            final RollingFileManager manager, final String fileName, final 
String filePattern,
-            final boolean ignoreExceptions, final boolean immediateFlush, 
final Advertiser advertiser) {
-        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
-        if (advertiser != null) {
-            final Map<String, String> configuration = new 
HashMap<>(layout.getContentFormat());
-            configuration.put("contentType", layout.getContentType());
-            configuration.put("name", name);
-            advertisement = advertiser.advertise(configuration);
-        }
-        this.fileName = fileName;
-        this.filePattern = filePattern;
-        this.advertiser = advertiser;
-    }
-
-    @Override
-    public boolean stop(final long timeout, final TimeUnit timeUnit) {
-        setStopping();
-        super.stop(timeout, timeUnit, false);
-        if (advertiser != null) {
-            advertiser.unadvertise(advertisement);
-        }
-        setStopped();
-        return true;
-    }
-
-    /**
-     * Writes the log entry rolling over the file when required.
-
-     * @param event The LogEvent.
-     */
-    @Override
-    public void append(final LogEvent event) {
-        getManager().checkRollover(event);
-        super.append(event);
-    }
-
-    /**
-     * Returns the File name for the Appender.
-     * @return The file name.
-     */
-    public String getFileName() {
-        return fileName;
-    }
-
-    /**
-     * Returns the file pattern used when rolling over.
-     * @return The file pattern.
-     */
-    public String getFilePattern() {
-        return filePattern;
-    }
-
-    /**
-     * Returns the triggering policy.
-     * @param <T> TriggeringPolicy type
-     * @return The TriggeringPolicy
-     */
-    public <T extends TriggeringPolicy> T getTriggeringPolicy() {
-        return getManager().getTriggeringPolicy();
-    }
-
-    /**
-     * Creates a RollingFileAppender.
-     * @param fileName The name of the file that is actively written to. 
(required).
-     * @param filePattern The pattern of the file name to use on rollover. 
(required).
-     * @param append If true, events are appended to the file. If false, the 
file
-     * is overwritten when opened. Defaults to "true"
-     * @param name The name of the Appender (required).
-     * @param bufferedIO When true, I/O will be buffered. Defaults to "true".
-     * @param bufferSizeStr buffer size for buffered IO (default is 8192).
-     * @param immediateFlush When true, events are immediately flushed. 
Defaults to "true".
-     * @param policy The triggering policy. (required).
-     * @param strategy The rollover strategy. Defaults to 
DefaultRolloverStrategy.
-     * @param layout The layout to use (defaults to the default PatternLayout).
-     * @param filter The Filter or null.
-     * @param ignore If {@code "true"} (default) exceptions encountered when 
appending events are logged; otherwise
-     *               they are propagated to the caller.
-     * @param advertise "true" if the appender configuration should be 
advertised, "false" otherwise.
-     * @param advertiseUri The advertised URI which can be used to retrieve 
the file contents.
-     * @param config The Configuration.
-     * @return A RollingFileAppender.
-     * @deprecated Use {@link #newBuilder()}.
-     */
-    @Deprecated
-    public static RollingFileAppender createAppender(
-            // @formatter:off
-            final String fileName,
-            final String filePattern,
-            final String append,
-            final String name,
-            final String bufferedIO,
-            final String bufferSizeStr,
-            final String immediateFlush,
-            final TriggeringPolicy policy,
-            final RolloverStrategy strategy,
-            final Layout<? extends Serializable> layout,
-            final Filter filter,
-            final String ignore,
-            final String advertise,
-            final String advertiseUri,
-            final Configuration config) {
-            // @formatter:on
-        final int bufferSize = Integers.parseInt(bufferSizeStr, 
DEFAULT_BUFFER_SIZE);
-        // @formatter:off
-        return newBuilder()
-                .withAdvertise(Boolean.parseBoolean(advertise))
-                .withAdvertiseUri(advertiseUri)
-                .withAppend(Booleans.parseBoolean(append, true))
-                .withBufferedIo(Booleans.parseBoolean(bufferedIO, true))
-                .withBufferSize(bufferSize)
-                .withConfiguration(config)
-                .withFileName(fileName)
-                .withFilePattern(filePattern)
-                .withFilter(filter)
-                .withIgnoreExceptions(Booleans.parseBoolean(ignore, true))
-                .withImmediateFlush(Booleans.parseBoolean(immediateFlush, 
true))
-                .withLayout(layout)
-                .withCreateOnDemand(false)
-                .withLocking(false)
-                .withName(name)
-                .withPolicy(policy)
-                .withStrategy(strategy)
-                .build();
-        // @formatter:on
-    }
-
-    @PluginBuilderFactory
-    public static <B extends Builder<B>> B newBuilder() {
-        return new Builder<B>().asBuilder();
-    }
-}
+/*
+ * 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.logging.log4j.core.appender;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.Deflater;
+
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
+import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.net.Advertiser;
+import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.core.util.Integers;
+
+/**
+ * An appender that writes to files and can roll over at intervals.
+ */
+@Plugin(name = "RollingFile", category = "Core", elementType = "appender", 
printObject = true)
+public final class RollingFileAppender extends 
AbstractOutputStreamAppender<RollingFileManager> {
+
+    /**
+     * Builds FileAppender instances.
+     * 
+     * @param <B>
+     *            This builder class
+     */
+    public static class Builder<B extends Builder<B>> extends 
AbstractOutputStreamAppender.Builder<B>
+            implements 
org.apache.logging.log4j.core.util.Builder<RollingFileAppender> {
+
+        @PluginBuilderAttribute
+        @Required
+        private String fileName;
+
+        @PluginBuilderAttribute
+        @Required
+        private String filePattern;
+
+        @PluginBuilderAttribute
+        private boolean append = true;
+
+        @PluginBuilderAttribute
+        private boolean locking;
+
+        @PluginElement("Policy") 
+        @Required
+        private TriggeringPolicy policy;
+        
+        @PluginElement("Strategy") 
+        private RolloverStrategy strategy;
+
+        @PluginBuilderAttribute
+        private boolean advertise;
+
+        @PluginBuilderAttribute
+        private String advertiseUri;
+
+        @PluginBuilderAttribute
+        private boolean createOnDemand;
+
+        @PluginConfiguration
+        private Configuration configuration;
+
+        @Override
+        public RollingFileAppender build() {
+            // Even though some variables may be annotated with @Required, we 
must still perform validation here for
+            // call sites that build builders programmatically.
+            final boolean isBufferedIo = isBufferedIo();
+            final int bufferSize = getBufferSize();
+            if (getName() == null) {
+                LOGGER.error("RollingFileAppender '{}': No name provided.", 
getName());
+                return null;
+            }
+
+            if (!isBufferedIo && bufferSize > 0) {
+                LOGGER.warn("RollingFileAppender '{}': The bufferSize is set 
to {} but bufferedIO is not true", getName(), bufferSize);
+            }
+
+            if (fileName == null) {
+                LOGGER.error("RollingFileAppender '{}': No file name 
provided.", getName());
+                return null;
+            }
+
+            if (filePattern == null) {
+                LOGGER.error("RollingFileAppender '{}': No file name pattern 
provided.", getName());
+                return null;
+            }
+
+            if (policy == null) {
+                LOGGER.error("RollingFileAppender '{}': No TriggeringPolicy 
provided.", getName());
+                return null;
+            }
+
+            if (strategy == null) {
+                strategy = DefaultRolloverStrategy.createStrategy(null, null, 
null,
+                        String.valueOf(Deflater.DEFAULT_COMPRESSION), null, 
true, configuration);
+            }
+
+            if (strategy == null) {
+                strategy = DefaultRolloverStrategy.createStrategy(null, null, 
null,
+                        String.valueOf(Deflater.DEFAULT_COMPRESSION), null, 
true, configuration);
+            }
+
+            final RollingFileManager manager = 
RollingFileManager.getFileManager(fileName, filePattern, append,
+                    isBufferedIo, policy, strategy, advertiseUri, getLayout(), 
bufferSize, isImmediateFlush(),
+                    createOnDemand, configuration);
+            if (manager == null) {
+                return null;
+            }
+
+            manager.initialize();
+
+            return new RollingFileAppender(getName(), getLayout(), 
getFilter(), manager, fileName, filePattern,
+                    isIgnoreExceptions(), isImmediateFlush(), advertise ? 
configuration.getAdvertiser() : null);
+        }
+
+        public String getAdvertiseUri() {
+            return advertiseUri;
+        }
+
+        public Configuration getConfiguration() {
+            return configuration;
+        }
+
+        public String getFileName() {
+            return fileName;
+        }
+
+        public boolean isAdvertise() {
+            return advertise;
+        }
+
+        public boolean isAppend() {
+            return append;
+        }
+
+        public boolean isCreateOnDemand() {
+            return createOnDemand;
+        }
+
+        public boolean isLocking() {
+            return locking;
+        }
+
+        public B withAdvertise(final boolean advertise) {
+            this.advertise = advertise;
+            return asBuilder();
+        }
+
+        public B withAdvertiseUri(final String advertiseUri) {
+            this.advertiseUri = advertiseUri;
+            return asBuilder();
+        }
+
+        public B withAppend(final boolean append) {
+            this.append = append;
+            return asBuilder();
+        }
+
+        public B withConfiguration(final Configuration config) {
+            this.configuration = config;
+            return asBuilder();
+        }
+
+        public B withFileName(final String fileName) {
+            this.fileName = fileName;
+            return asBuilder();
+        }
+
+        public B withCreateOnDemand(final boolean createOnDemand) {
+            this.createOnDemand = createOnDemand;
+            return asBuilder();
+        }
+
+        public B withLocking(final boolean locking) {
+            this.locking = locking;
+            return asBuilder();
+        }
+
+        public String getFilePattern() {
+            return filePattern;
+        }
+
+        public TriggeringPolicy getPolicy() {
+            return policy;
+        }
+
+        public RolloverStrategy getStrategy() {
+            return strategy;
+        }
+
+        public B withFilePattern(final String filePattern) {
+            this.filePattern = filePattern;
+            return asBuilder();
+        }
+
+        public B withPolicy(final TriggeringPolicy policy) {
+            this.policy = policy;
+            return asBuilder();
+        }
+
+        public B withStrategy(final RolloverStrategy strategy) {
+            this.strategy = strategy;
+            return asBuilder();
+        }
+
+    }
+    
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    private final String fileName;
+    private final String filePattern;
+    private Object advertisement;
+    private final Advertiser advertiser;
+
+    private RollingFileAppender(final String name, final Layout<? extends 
Serializable> layout, final Filter filter,
+            final RollingFileManager manager, final String fileName, final 
String filePattern,
+            final boolean ignoreExceptions, final boolean immediateFlush, 
final Advertiser advertiser) {
+        super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+        if (advertiser != null) {
+            final Map<String, String> configuration = new 
HashMap<>(layout.getContentFormat());
+            configuration.put("contentType", layout.getContentType());
+            configuration.put("name", name);
+            advertisement = advertiser.advertise(configuration);
+        }
+        this.fileName = fileName;
+        this.filePattern = filePattern;
+        this.advertiser = advertiser;
+    }
+
+    @Override
+    public boolean stop(final long timeout, final TimeUnit timeUnit) {
+        setStopping();
+        final boolean stopped = super.stop(timeout, timeUnit, false);
+        if (advertiser != null) {
+            advertiser.unadvertise(advertisement);
+        }
+        setStopped();
+        return stopped;
+    }
+
+    /**
+     * Writes the log entry rolling over the file when required.
+
+     * @param event The LogEvent.
+     */
+    @Override
+    public void append(final LogEvent event) {
+        getManager().checkRollover(event);
+        super.append(event);
+    }
+
+    /**
+     * Returns the File name for the Appender.
+     * @return The file name.
+     */
+    public String getFileName() {
+        return fileName;
+    }
+
+    /**
+     * Returns the file pattern used when rolling over.
+     * @return The file pattern.
+     */
+    public String getFilePattern() {
+        return filePattern;
+    }
+
+    /**
+     * Returns the triggering policy.
+     * @param <T> TriggeringPolicy type
+     * @return The TriggeringPolicy
+     */
+    public <T extends TriggeringPolicy> T getTriggeringPolicy() {
+        return getManager().getTriggeringPolicy();
+    }
+
+    /**
+     * Creates a RollingFileAppender.
+     * @param fileName The name of the file that is actively written to. 
(required).
+     * @param filePattern The pattern of the file name to use on rollover. 
(required).
+     * @param append If true, events are appended to the file. If false, the 
file
+     * is overwritten when opened. Defaults to "true"
+     * @param name The name of the Appender (required).
+     * @param bufferedIO When true, I/O will be buffered. Defaults to "true".
+     * @param bufferSizeStr buffer size for buffered IO (default is 8192).
+     * @param immediateFlush When true, events are immediately flushed. 
Defaults to "true".
+     * @param policy The triggering policy. (required).
+     * @param strategy The rollover strategy. Defaults to 
DefaultRolloverStrategy.
+     * @param layout The layout to use (defaults to the default PatternLayout).
+     * @param filter The Filter or null.
+     * @param ignore If {@code "true"} (default) exceptions encountered when 
appending events are logged; otherwise
+     *               they are propagated to the caller.
+     * @param advertise "true" if the appender configuration should be 
advertised, "false" otherwise.
+     * @param advertiseUri The advertised URI which can be used to retrieve 
the file contents.
+     * @param config The Configuration.
+     * @return A RollingFileAppender.
+     * @deprecated Use {@link #newBuilder()}.
+     */
+    @Deprecated
+    public static RollingFileAppender createAppender(
+            // @formatter:off
+            final String fileName,
+            final String filePattern,
+            final String append,
+            final String name,
+            final String bufferedIO,
+            final String bufferSizeStr,
+            final String immediateFlush,
+            final TriggeringPolicy policy,
+            final RolloverStrategy strategy,
+            final Layout<? extends Serializable> layout,
+            final Filter filter,
+            final String ignore,
+            final String advertise,
+            final String advertiseUri,
+            final Configuration config) {
+            // @formatter:on
+        final int bufferSize = Integers.parseInt(bufferSizeStr, 
DEFAULT_BUFFER_SIZE);
+        // @formatter:off
+        return newBuilder()
+                .withAdvertise(Boolean.parseBoolean(advertise))
+                .withAdvertiseUri(advertiseUri)
+                .withAppend(Booleans.parseBoolean(append, true))
+                .withBufferedIo(Booleans.parseBoolean(bufferedIO, true))
+                .withBufferSize(bufferSize)
+                .withConfiguration(config)
+                .withFileName(fileName)
+                .withFilePattern(filePattern)
+                .withFilter(filter)
+                .withIgnoreExceptions(Booleans.parseBoolean(ignore, true))
+                .withImmediateFlush(Booleans.parseBoolean(immediateFlush, 
true))
+                .withLayout(layout)
+                .withCreateOnDemand(false)
+                .withLocking(false)
+                .withName(name)
+                .withPolicy(policy)
+                .withStrategy(strategy)
+                .build();
+        // @formatter:on
+    }
+
+    @PluginBuilderFactory
+    public static <B extends Builder<B>> B newBuilder() {
+        return new Builder<B>().asBuilder();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java
index 323a9fe..c2e7572 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java
@@ -1,150 +1,151 @@
-/*
- * 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.logging.log4j.core.appender;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.logging.log4j.core.StringLayout;
-
-/**
- * Manages a Writer so that it can be shared by multiple Appenders and will
- * allow appenders to reconfigure without requiring a new writer.
- */
-public class WriterManager extends AbstractManager {
-
-    /**
-     * Creates a Manager.
-     *
-     * @param name The name of the stream to manage.
-     * @param data The data to pass to the Manager.
-     * @param factory The factory to use to create the Manager.
-     * @param <T> The type of the WriterManager.
-     * @return A WriterManager.
-     */
-    public static <T> WriterManager getManager(final String name, final T data,
-                                                 final ManagerFactory<? 
extends WriterManager, T> factory) {
-        return AbstractManager.getManager(name, factory, data);
-    }
-    protected final StringLayout layout;
-
-    private volatile Writer writer;
-
-    public WriterManager(final Writer writer, final String streamName, final 
StringLayout layout,
-            final boolean writeHeader) {
-        super(null, streamName);
-        this.writer = writer;
-        this.layout = layout;
-        if (writeHeader && layout != null) {
-            final byte[] header = layout.getHeader();
-            if (header != null) {
-                try {
-                    this.writer.write(new String(header, layout.getCharset()));
-                } catch (final IOException e) {
-                    logError("Unable to write header", e);
-                }
-            }
-        }
-    }
-
-    protected synchronized void closeWriter() {
-        final Writer w = writer; // access volatile field only once per method
-        try {
-            w.close();
-        } catch (final IOException ex) {
-            logError("Unable to close stream", ex);
-        }
-    }
-
-    /**
-     * Flushes any buffers.
-     */
-    public synchronized void flush() {
-        try {
-            writer.flush();
-        } catch (final IOException ex) {
-            final String msg = "Error flushing stream " + getName();
-            throw new AppenderLoggingException(msg, ex);
-        }
-    }
-
-    protected Writer getWriter() {
-        return writer;
-    }
-
-    /**
-     * Returns the status of the stream.
-     * @return true if the stream is open, false if it is not.
-     */
-    public boolean isOpen() {
-        return getCount() > 0;
-    }
-
-    /**
-     * Default hook to write footer during close.
-     */
-    @Override
-    public void releaseSub(final long timeout, final TimeUnit timeUnit) {
-        writeFooter();
-        closeWriter();
-    }
-
-    protected void setWriter(final Writer writer) {
-        final byte[] header = layout.getHeader();
-        if (header != null) {
-            try {
-                writer.write(new String(header, layout.getCharset()));
-                this.writer = writer; // only update field if writer.write() 
succeeded
-            } catch (final IOException ioe) {
-                logError("Unable to write header", ioe);
-            }
-        } else {
-            this.writer = writer;
-        }
-    }
-
-    /**
-     * Some output streams synchronize writes while others do not. 
Synchronizing here insures that
-     * log events won't be intertwined.
-     * @param bytes The serialized Log event.
-     * @param offset The offset into the byte array.
-     * @param length The number of bytes to write.
-     * @throws AppenderLoggingException if an error occurs.
-     */
-    protected synchronized void write(final String str)  {
-        try {
-            writer.write(str);
-        } catch (final IOException ex) {
-            final String msg = "Error writing to stream " + getName();
-            throw new AppenderLoggingException(msg, ex);
-        }
-    }
-
-    /**
-     * Writes the footer.
-     */
-    protected void writeFooter() {
-        if (layout == null) {
-            return;
-        }
-        final byte[] footer = layout.getFooter();
-        if (footer != null && footer.length > 0) {
-            write(new String(footer, layout.getCharset()));
-        }
-    }
-}
+/*
+ * 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.logging.log4j.core.appender;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.core.StringLayout;
+
+/**
+ * Manages a Writer so that it can be shared by multiple Appenders and will
+ * allow appenders to reconfigure without requiring a new writer.
+ */
+public class WriterManager extends AbstractManager {
+
+    /**
+     * Creates a Manager.
+     *
+     * @param name The name of the stream to manage.
+     * @param data The data to pass to the Manager.
+     * @param factory The factory to use to create the Manager.
+     * @param <T> The type of the WriterManager.
+     * @return A WriterManager.
+     */
+    public static <T> WriterManager getManager(final String name, final T data,
+                                                 final ManagerFactory<? 
extends WriterManager, T> factory) {
+        return AbstractManager.getManager(name, factory, data);
+    }
+    protected final StringLayout layout;
+
+    private volatile Writer writer;
+
+    public WriterManager(final Writer writer, final String streamName, final 
StringLayout layout,
+            final boolean writeHeader) {
+        super(null, streamName);
+        this.writer = writer;
+        this.layout = layout;
+        if (writeHeader && layout != null) {
+            final byte[] header = layout.getHeader();
+            if (header != null) {
+                try {
+                    this.writer.write(new String(header, layout.getCharset()));
+                } catch (final IOException e) {
+                    logError("Unable to write header", e);
+                }
+            }
+        }
+    }
+
+    protected synchronized void closeWriter() {
+        final Writer w = writer; // access volatile field only once per method
+        try {
+            w.close();
+        } catch (final IOException ex) {
+            logError("Unable to close stream", ex);
+        }
+    }
+
+    /**
+     * Flushes any buffers.
+     */
+    public synchronized void flush() {
+        try {
+            writer.flush();
+        } catch (final IOException ex) {
+            final String msg = "Error flushing stream " + getName();
+            throw new AppenderLoggingException(msg, ex);
+        }
+    }
+
+    protected Writer getWriter() {
+        return writer;
+    }
+
+    /**
+     * Returns the status of the stream.
+     * @return true if the stream is open, false if it is not.
+     */
+    public boolean isOpen() {
+        return getCount() > 0;
+    }
+
+    /**
+     * Default hook to write footer during close.
+     */
+    @Override
+    public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
+        writeFooter();
+        closeWriter();
+        return true;
+    }
+
+    protected void setWriter(final Writer writer) {
+        final byte[] header = layout.getHeader();
+        if (header != null) {
+            try {
+                writer.write(new String(header, layout.getCharset()));
+                this.writer = writer; // only update field if writer.write() 
succeeded
+            } catch (final IOException ioe) {
+                logError("Unable to write header", ioe);
+            }
+        } else {
+            this.writer = writer;
+        }
+    }
+
+    /**
+     * Some output streams synchronize writes while others do not. 
Synchronizing here insures that
+     * log events won't be intertwined.
+     * @param bytes The serialized Log event.
+     * @param offset The offset into the byte array.
+     * @param length The number of bytes to write.
+     * @throws AppenderLoggingException if an error occurs.
+     */
+    protected synchronized void write(final String str)  {
+        try {
+            writer.write(str);
+        } catch (final IOException ex) {
+            final String msg = "Error writing to stream " + getName();
+            throw new AppenderLoggingException(msg, ex);
+        }
+    }
+
+    /**
+     * Writes the footer.
+     */
+    protected void writeFooter() {
+        if (layout == null) {
+            return;
+        }
+        final byte[] footer = layout.getFooter();
+        if (footer != null && footer.length > 0) {
+            write(new String(footer, layout.getCharset()));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
index 85a3a67..538b644 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
@@ -1,142 +1,142 @@
-/*
- * 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.logging.log4j.core.appender.db;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.logging.log4j.LoggingException;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.appender.AppenderLoggingException;
-
-/**
- * An abstract Appender for writing events to a database of some type, be it 
relational or NoSQL. All database appenders
- * should inherit from this base appender. Three implementations are currently 
provided:
- * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link 
org.apache.logging.log4j.core.appender.db.jpa
- * JPA}, and <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>.
- *
- * @param <T> Specifies which type of {@link AbstractDatabaseManager} this 
Appender requires.
- */
-public abstract class AbstractDatabaseAppender<T extends 
AbstractDatabaseManager> extends AbstractAppender {
-
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
-    private final Lock readLock = lock.readLock();
-    private final Lock writeLock = lock.writeLock();
-
-    private T manager;
-
-    /**
-     * Instantiates the base appender.
-     *
-     * @param name The appender name.
-     * @param filter The filter, if any, to use.
-     * @param ignoreExceptions If {@code true} exceptions encountered when 
appending events are logged; otherwise
-     *                         they are propagated to the caller.
-     * @param manager The matching {@link AbstractDatabaseManager} 
implementation.
-     */
-    protected AbstractDatabaseAppender(final String name, final Filter filter, 
final boolean ignoreExceptions,
-                                       final T manager) {
-        super(name, filter, null, ignoreExceptions);
-        this.manager = manager;
-    }
-
-    /**
-     * This always returns {@code null}, as database appenders do not use a 
single layout. The JPA and NoSQL appenders
-     * do not use a layout at all. The JDBC appender has a layout-per-column 
pattern.
-     *
-     * @return {@code null}.
-     */
-    @Override
-    public final Layout<LogEvent> getLayout() {
-        return null;
-    }
-
-    /**
-     * Returns the underlying manager in use within this appender.
-     *
-     * @return the manager.
-     */
-    public final T getManager() {
-        return this.manager;
-    }
-
-    @Override
-    public final void start() {
-        if (this.getManager() == null) {
-            LOGGER.error("No AbstractDatabaseManager set for the appender 
named [{}].", this.getName());
-        }
-        super.start();
-        if (this.getManager() != null) {
-            this.getManager().startup();
-        }
-    }
-
-    @Override
-    public boolean stop(final long timeout, final TimeUnit timeUnit) {
-        setStopping();
-        super.stop(timeout, timeUnit, false);
-        if (this.getManager() != null) {
-            this.getManager().stop(timeout, timeUnit);
-        }
-        setStopped();
-        return true;
-    }
-
-    @Override
-    public final void append(final LogEvent event) {
-        this.readLock.lock();
-        try {
-            this.getManager().write(event);
-        } catch (final LoggingException e) {
-            LOGGER.error("Unable to write to database [{}] for appender 
[{}].", this.getManager().getName(),
-                    this.getName(), e);
-            throw e;
-        } catch (final Exception e) {
-            LOGGER.error("Unable to write to database [{}] for appender 
[{}].", this.getManager().getName(),
-                    this.getName(), e);
-            throw new AppenderLoggingException("Unable to write to database in 
appender: " + e.getMessage(), e);
-        } finally {
-            this.readLock.unlock();
-        }
-    }
-
-    /**
-     * Replaces the underlying manager in use within this appender. This can 
be useful for manually changing the way log
-     * events are written to the database without losing buffered or 
in-progress events. The existing manager is
-     * released only after the new manager has been installed. This method is 
thread-safe.
-     *
-     * @param manager The new manager to install.
-     */
-    protected final void replaceManager(final T manager) {
-        this.writeLock.lock();
-        try {
-            final T old = this.getManager();
-            if (!manager.isRunning()) {
-                manager.startup();
-            }
-            this.manager = manager;
-            old.close();
-        } finally {
-            this.writeLock.unlock();
-        }
-    }
-}
+/*
+ * 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.logging.log4j.core.appender.db;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.logging.log4j.LoggingException;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+
+/**
+ * An abstract Appender for writing events to a database of some type, be it 
relational or NoSQL. All database appenders
+ * should inherit from this base appender. Three implementations are currently 
provided:
+ * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link 
org.apache.logging.log4j.core.appender.db.jpa
+ * JPA}, and <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>.
+ *
+ * @param <T> Specifies which type of {@link AbstractDatabaseManager} this 
Appender requires.
+ */
+public abstract class AbstractDatabaseAppender<T extends 
AbstractDatabaseManager> extends AbstractAppender {
+
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private final Lock readLock = lock.readLock();
+    private final Lock writeLock = lock.writeLock();
+
+    private T manager;
+
+    /**
+     * Instantiates the base appender.
+     *
+     * @param name The appender name.
+     * @param filter The filter, if any, to use.
+     * @param ignoreExceptions If {@code true} exceptions encountered when 
appending events are logged; otherwise
+     *                         they are propagated to the caller.
+     * @param manager The matching {@link AbstractDatabaseManager} 
implementation.
+     */
+    protected AbstractDatabaseAppender(final String name, final Filter filter, 
final boolean ignoreExceptions,
+                                       final T manager) {
+        super(name, filter, null, ignoreExceptions);
+        this.manager = manager;
+    }
+
+    /**
+     * This always returns {@code null}, as database appenders do not use a 
single layout. The JPA and NoSQL appenders
+     * do not use a layout at all. The JDBC appender has a layout-per-column 
pattern.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public final Layout<LogEvent> getLayout() {
+        return null;
+    }
+
+    /**
+     * Returns the underlying manager in use within this appender.
+     *
+     * @return the manager.
+     */
+    public final T getManager() {
+        return this.manager;
+    }
+
+    @Override
+    public final void start() {
+        if (this.getManager() == null) {
+            LOGGER.error("No AbstractDatabaseManager set for the appender 
named [{}].", this.getName());
+        }
+        super.start();
+        if (this.getManager() != null) {
+            this.getManager().startup();
+        }
+    }
+
+    @Override
+    public boolean stop(final long timeout, final TimeUnit timeUnit) {
+        setStopping();
+        boolean stopped = super.stop(timeout, timeUnit, false);
+        if (this.getManager() != null) {
+            stopped &= this.getManager().stop(timeout, timeUnit);
+        }
+        setStopped();
+        return stopped;
+    }
+
+    @Override
+    public final void append(final LogEvent event) {
+        this.readLock.lock();
+        try {
+            this.getManager().write(event);
+        } catch (final LoggingException e) {
+            LOGGER.error("Unable to write to database [{}] for appender 
[{}].", this.getManager().getName(),
+                    this.getName(), e);
+            throw e;
+        } catch (final Exception e) {
+            LOGGER.error("Unable to write to database [{}] for appender 
[{}].", this.getManager().getName(),
+                    this.getName(), e);
+            throw new AppenderLoggingException("Unable to write to database in 
appender: " + e.getMessage(), e);
+        } finally {
+            this.readLock.unlock();
+        }
+    }
+
+    /**
+     * Replaces the underlying manager in use within this appender. This can 
be useful for manually changing the way log
+     * events are written to the database without losing buffered or 
in-progress events. The existing manager is
+     * released only after the new manager has been installed. This method is 
thread-safe.
+     *
+     * @param manager The new manager to install.
+     */
+    protected final void replaceManager(final T manager) {
+        this.writeLock.lock();
+        try {
+            final T old = this.getManager();
+            if (!manager.isRunning()) {
+                manager.startup();
+            }
+            this.manager = manager;
+            old.close();
+        } finally {
+            this.writeLock.unlock();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
index 5f46bf4..8400adb 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
@@ -1,226 +1,232 @@
-/*
- * 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.logging.log4j.core.appender.db;
-
-import java.io.Flushable;
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.AbstractManager;
-import org.apache.logging.log4j.core.appender.ManagerFactory;
-
-/**
- * Manager that allows database appenders to have their configuration reloaded 
without losing events.
- */
-public abstract class AbstractDatabaseManager extends AbstractManager 
implements Flushable {
-    private final ArrayList<LogEvent> buffer;
-    private final int bufferSize;
-
-    private boolean running = false;
-
-    /**
-     * Instantiates the base manager.
-     *
-     * @param name The manager name, which should include any configuration 
details that one might want to be able to
-     *             reconfigure at runtime, such as database name, username, 
(hashed) password, etc.
-     * @param bufferSize The size of the log event buffer.
-     */
-    protected AbstractDatabaseManager(final String name, final int bufferSize) 
{
-        super(null, name);
-        this.bufferSize = bufferSize;
-        this.buffer = new ArrayList<>(bufferSize + 1);
-    }
-
-    /**
-     * Implementations should implement this method to perform any proprietary 
startup operations. This method will
-     * never be called twice on the same instance. It is safe to throw any 
exceptions from this method. This method
-     * does not necessarily connect to the database, as it is generally 
unreliable to connect once and use the same
-     * connection for hours.
-     */
-    protected abstract void startupInternal() throws Exception;
-
-    /**
-     * This method is called within the appender when the appender is started. 
If it has not already been called, it
-     * calls {@link #startupInternal()} and catches any exceptions it might 
throw.
-     */
-    public final synchronized void startup() {
-        if (!this.isRunning()) {
-            try {
-                this.startupInternal();
-                this.running = true;
-            } catch (final Exception e) {
-                logError("Could not perform database startup operations", e);
-            }
-        }
-    }
-
-    /**
-     * Implementations should implement this method to perform any proprietary 
disconnection / shutdown operations. This
-     * method will never be called twice on the same instance, and it will 
only be called <em>after</em>
-     * {@link #startupInternal()}. It is safe to throw any exceptions from 
this method. This method does not
-     * necessarily disconnect from the database for the same reasons outlined 
in {@link #startupInternal()}.
-     */
-    protected abstract void shutdownInternal() throws Exception;
-
-    /**
-     * This method is called from the {@link #close()} method when the 
appender is stopped or the appender's manager
-     * is replaced. If it has not already been called, it calls {@link 
#shutdownInternal()} and catches any exceptions
-     * it might throw.
-     */
-    public final synchronized void shutdown() {
-        this.flush();
-        if (this.isRunning()) {
-            try {
-                this.shutdownInternal();
-            } catch (final Exception e) {
-                logWarn("Caught exception while performing database shutdown 
operations", e);
-            } finally {
-                this.running = false;
-            }
-        }
-    }
-
-    /**
-     * Indicates whether the manager is currently connected {@link #startup()} 
has been called and {@link #shutdown()}
-     * has not been called).
-     *
-     * @return {@code true} if the manager is connected.
-     */
-    public final boolean isRunning() {
-        return this.running;
-    }
-
-    /**
-     * Connects to the database and starts a transaction (if applicable). With 
buffering enabled, this is called when
-     * flushing the buffer begins, before the first call to {@link 
#writeInternal}. With buffering disabled, this is
-     * called immediately before every invocation of {@link #writeInternal}.
-     */
-    protected abstract void connectAndStart();
-
-    /**
-     * Performs the actual writing of the event in an implementation-specific 
way. This method is called immediately
-     * from {@link #write(LogEvent)} if buffering is off, or from {@link 
#flush()} if the buffer has reached its limit.
-     *
-     * @param event The event to write to the database.
-     */
-    protected abstract void writeInternal(LogEvent event);
-
-    /**
-     * Commits any active transaction (if applicable) and disconnects from the 
database (returns the connection to the
-     * connection pool). With buffering enabled, this is called when flushing 
the buffer completes, after the last call
-     * to {@link #writeInternal}. With buffering disabled, this is called 
immediately after every invocation of
-     * {@link #writeInternal}.
-     */
-    protected abstract void commitAndClose();
-
-    /**
-     * This method is called automatically when the buffer size reaches its 
maximum or at the beginning of a call to
-     * {@link #shutdown()}. It can also be called manually to flush events to 
the database.
-     */
-    @Override
-    public final synchronized void flush() {
-        if (this.isRunning() && this.buffer.size() > 0) {
-            this.connectAndStart();
-            try {
-                for (final LogEvent event : this.buffer) {
-                    this.writeInternal(event);
-                }
-            } finally {
-                this.commitAndClose();
-                // not sure if this should be done when writing the events 
failed
-                this.buffer.clear();
-            }
-        }
-    }
-
-    /**
-     * This method manages buffering and writing of events.
-     *
-     * @param event The event to write to the database.
-     */
-    public final synchronized void write(final LogEvent event) {
-        if (this.bufferSize > 0) {
-            this.buffer.add(event);
-            if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) 
{
-                this.flush();
-            }
-        } else {
-            this.connectAndStart();
-            try {
-                this.writeInternal(event);
-            } finally {
-                this.commitAndClose();
-            }
-        }
-    }
-
-    @Override
-    public final void releaseSub(final long timeout, final TimeUnit timeUnit) {
-        this.shutdown();
-    }
-
-    @Override
-    public final String toString() {
-        return this.getName();
-    }
-
-    /**
-     * Implementations should define their own getManager method and call this 
method from that to create or get
-     * existing managers.
-     *
-     * @param name The manager name, which should include any configuration 
details that one might want to be able to
-     *             reconfigure at runtime, such as database name, username, 
(hashed) password, etc.
-     * @param data The concrete instance of {@link AbstractFactoryData} 
appropriate for the given manager.
-     * @param factory A factory instance for creating the appropriate manager.
-     * @param <M> The concrete manager type.
-     * @param <T> The concrete {@link AbstractFactoryData} type.
-     * @return a new or existing manager of the specified type and name.
-     */
-    protected static <M extends AbstractDatabaseManager, T extends 
AbstractFactoryData> M getManager(
-            final String name, final T data, final ManagerFactory<M, T> factory
-    ) {
-        return AbstractManager.getManager(name, factory, data);
-    }
-
-    /**
-     * Implementations should extend this class for passing data between the 
getManager method and the manager factory
-     * class.
-     */
-    protected abstract static class AbstractFactoryData {
-        private final int bufferSize;
-
-        /**
-         * Constructs the base factory data.
-         *
-         * @param bufferSize The size of the buffer.
-         */
-        protected AbstractFactoryData(final int bufferSize) {
-            this.bufferSize = bufferSize;
-        }
-
-        /**
-         * Gets the buffer size.
-         *
-         * @return the buffer size.
-         */
-        public int getBufferSize() {
-            return bufferSize;
-        }
-    }
-}
+/*
+ * 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.logging.log4j.core.appender.db;
+
+import java.io.Flushable;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractManager;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+
+/**
+ * Manager that allows database appenders to have their configuration reloaded 
without losing events.
+ */
+public abstract class AbstractDatabaseManager extends AbstractManager 
implements Flushable {
+    private final ArrayList<LogEvent> buffer;
+    private final int bufferSize;
+
+    private boolean running = false;
+
+    /**
+     * Instantiates the base manager.
+     *
+     * @param name The manager name, which should include any configuration 
details that one might want to be able to
+     *             reconfigure at runtime, such as database name, username, 
(hashed) password, etc.
+     * @param bufferSize The size of the log event buffer.
+     */
+    protected AbstractDatabaseManager(final String name, final int bufferSize) 
{
+        super(null, name);
+        this.bufferSize = bufferSize;
+        this.buffer = new ArrayList<>(bufferSize + 1);
+    }
+
+    /**
+     * Implementations should implement this method to perform any proprietary 
startup operations. This method will
+     * never be called twice on the same instance. It is safe to throw any 
exceptions from this method. This method
+     * does not necessarily connect to the database, as it is generally 
unreliable to connect once and use the same
+     * connection for hours.
+     */
+    protected abstract void startupInternal() throws Exception;
+
+    /**
+     * This method is called within the appender when the appender is started. 
If it has not already been called, it
+     * calls {@link #startupInternal()} and catches any exceptions it might 
throw.
+     */
+    public final synchronized void startup() {
+        if (!this.isRunning()) {
+            try {
+                this.startupInternal();
+                this.running = true;
+            } catch (final Exception e) {
+                logError("Could not perform database startup operations", e);
+            }
+        }
+    }
+
+    /**
+     * Implementations should implement this method to perform any proprietary 
disconnection / shutdown operations. This
+     * method will never be called twice on the same instance, and it will 
only be called <em>after</em>
+     * {@link #startupInternal()}. It is safe to throw any exceptions from 
this method. This method does not
+     * necessarily disconnect from the database for the same reasons outlined 
in {@link #startupInternal()}.
+     * @return true if all resources were closed normally, false otherwise.
+     */
+    protected abstract boolean shutdownInternal() throws Exception;
+
+    /**
+     * This method is called from the {@link #close()} method when the 
appender is stopped or the appender's manager
+     * is replaced. If it has not already been called, it calls {@link 
#shutdownInternal()} and catches any exceptions
+     * it might throw.
+     * @return true if all resources were closed normally, false otherwise.
+     */
+    public final synchronized boolean shutdown() {
+        boolean closed = true;
+        this.flush();
+        if (this.isRunning()) {
+            try {
+                this.shutdownInternal();
+            } catch (final Exception e) {
+                logWarn("Caught exception while performing database shutdown 
operations", e);
+                closed = false;
+            } finally {
+                this.running = false;
+            }
+        }
+        return closed;
+    }
+
+    /**
+     * Indicates whether the manager is currently connected {@link #startup()} 
has been called and {@link #shutdown()}
+     * has not been called).
+     *
+     * @return {@code true} if the manager is connected.
+     */
+    public final boolean isRunning() {
+        return this.running;
+    }
+
+    /**
+     * Connects to the database and starts a transaction (if applicable). With 
buffering enabled, this is called when
+     * flushing the buffer begins, before the first call to {@link 
#writeInternal}. With buffering disabled, this is
+     * called immediately before every invocation of {@link #writeInternal}.
+     */
+    protected abstract void connectAndStart();
+
+    /**
+     * Performs the actual writing of the event in an implementation-specific 
way. This method is called immediately
+     * from {@link #write(LogEvent)} if buffering is off, or from {@link 
#flush()} if the buffer has reached its limit.
+     *
+     * @param event The event to write to the database.
+     */
+    protected abstract void writeInternal(LogEvent event);
+
+    /**
+     * Commits any active transaction (if applicable) and disconnects from the 
database (returns the connection to the
+     * connection pool). With buffering enabled, this is called when flushing 
the buffer completes, after the last call
+     * to {@link #writeInternal}. With buffering disabled, this is called 
immediately after every invocation of
+     * {@link #writeInternal}.
+     * @return true if all resources were closed normally, false otherwise.
+     */
+    protected abstract boolean commitAndClose();
+
+    /**
+     * This method is called automatically when the buffer size reaches its 
maximum or at the beginning of a call to
+     * {@link #shutdown()}. It can also be called manually to flush events to 
the database.
+     */
+    @Override
+    public final synchronized void flush() {
+        if (this.isRunning() && this.buffer.size() > 0) {
+            this.connectAndStart();
+            try {
+                for (final LogEvent event : this.buffer) {
+                    this.writeInternal(event);
+                }
+            } finally {
+                this.commitAndClose();
+                // not sure if this should be done when writing the events 
failed
+                this.buffer.clear();
+            }
+        }
+    }
+
+    /**
+     * This method manages buffering and writing of events.
+     *
+     * @param event The event to write to the database.
+     */
+    public final synchronized void write(final LogEvent event) {
+        if (this.bufferSize > 0) {
+            this.buffer.add(event);
+            if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) 
{
+                this.flush();
+            }
+        } else {
+            this.connectAndStart();
+            try {
+                this.writeInternal(event);
+            } finally {
+                this.commitAndClose();
+            }
+        }
+    }
+
+    @Override
+    public final boolean releaseSub(final long timeout, final TimeUnit 
timeUnit) {
+        return this.shutdown();
+    }
+
+    @Override
+    public final String toString() {
+        return this.getName();
+    }
+
+    /**
+     * Implementations should define their own getManager method and call this 
method from that to create or get
+     * existing managers.
+     *
+     * @param name The manager name, which should include any configuration 
details that one might want to be able to
+     *             reconfigure at runtime, such as database name, username, 
(hashed) password, etc.
+     * @param data The concrete instance of {@link AbstractFactoryData} 
appropriate for the given manager.
+     * @param factory A factory instance for creating the appropriate manager.
+     * @param <M> The concrete manager type.
+     * @param <T> The concrete {@link AbstractFactoryData} type.
+     * @return a new or existing manager of the specified type and name.
+     */
+    protected static <M extends AbstractDatabaseManager, T extends 
AbstractFactoryData> M getManager(
+            final String name, final T data, final ManagerFactory<M, T> factory
+    ) {
+        return AbstractManager.getManager(name, factory, data);
+    }
+
+    /**
+     * Implementations should extend this class for passing data between the 
getManager method and the manager factory
+     * class.
+     */
+    protected abstract static class AbstractFactoryData {
+        private final int bufferSize;
+
+        /**
+         * Constructs the base factory data.
+         *
+         * @param bufferSize The size of the buffer.
+         */
+        protected AbstractFactoryData(final int bufferSize) {
+            this.bufferSize = bufferSize;
+        }
+
+        /**
+         * Gets the buffer size.
+         *
+         * @return the buffer size.
+         */
+        public int getBufferSize() {
+            return bufferSize;
+        }
+    }
+}

Reply via email to