Add JMS appenders

git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers@1137270 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/log4j2-core/pom.xml b/log4j2-core/pom.xml
index 8e41ec1..e9e9066 100644
--- a/log4j2-core/pom.xml
+++ b/log4j2-core/pom.xml
@@ -31,12 +31,6 @@
   <url>http://logging.apache.org/log4j/experimental</url>
   <dependencies>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.3.1</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
       <groupId>org.apache.logging</groupId>
       <artifactId>log4j2-api</artifactId>
       <version>1.99.0-SNAPSHOT</version>
@@ -64,6 +58,18 @@
       <artifactId>slf4j-api</artifactId>
       <scope>test</scope>
     </dependency>
+     <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.3.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockejb</groupId>
+      <artifactId>mockejb</artifactId>
+      <version>0.6-beta2</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>ch.qos.logback</groupId>
       <artifactId>logback-core</artifactId>
@@ -74,7 +80,12 @@
       <artifactId>logback-classic</artifactId>
       <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>
   <build>
     <plugins>
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/Layout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/Layout.java
index fe28fb8..e35754d 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/Layout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/Layout.java
@@ -1,12 +1,14 @@
 package org.apache.logging.log4j.core;
 
+import java.io.Serializable;
+
 /**
  * @doubt There is still a need for a character-based layout for character based event sinks (databases, etc).
  *  Would introduce an EventEncoder, EventRenderer or something similar for the logging event to byte encoding.
  * (RG) A layout can be configured with a Charset and then Strings can be converted to byte arrays. OTOH, it isn't
  * possible to write byte arrays as character streams.
  */
-public interface Layout {
+public interface Layout<T extends Serializable> {
     // Note that the line.separator property can be looked up even by
     // applets.
     /**
@@ -26,6 +28,13 @@
     byte[] format(LogEvent event);
 
     /**
+     * Formats the event as an Object that can be serialized.
+     * @param event The Logging Event.
+     * @return The formatted event.
+     */
+    T formatAs(LogEvent event);
+
+    /**
      * Returns the header for the layout format.
      * @return The header.
      * @doubt the concept of header and footer is not universal, should not be on the base interface.
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
index 442e87d..9fe048c 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
@@ -4,13 +4,14 @@
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.message.Message;
 
+import java.io.Serializable;
 import java.util.Map;
 import java.util.Stack;
 
 /**
  *
  */
-public interface LogEvent {
+public interface LogEvent extends Serializable {
 
      /**
      * Get level.
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
index 169a3cb..4edd2f2 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
@@ -53,6 +53,9 @@
             T manager = (T) map.get(name);
             if (manager == null) {
                 manager = factory.createManager(name, data);
+                if (manager == null) {
+                    throw new IllegalStateException("Unable to create a manager");
+                }
                 map.put(name, manager);
             }
             manager.count++;
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/JMSQueueAppender.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/JMSQueueAppender.java
new file mode 100644
index 0000000..86e2c4b
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/JMSQueueAppender.java
@@ -0,0 +1,79 @@
+/*
+ * 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 org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttr;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.filter.Filters;
+import org.apache.logging.log4j.core.layout.SerializedLayout;
+import org.apache.logging.log4j.core.net.JMSQueueManager;
+
+/**
+ *
+ */
+@Plugin(name="JMSQueue",type="Core",elementType="appender",printObject=true)
+public class JMSQueueAppender extends AppenderBase {
+
+    private final JMSQueueManager manager;
+
+    public JMSQueueAppender(String name, Filters filters, Layout layout, JMSQueueManager manager) {
+        super(name, filters, layout);
+        this.manager = manager;
+    }
+
+    /**
+     * Actual writing occurs here.
+     * <p/>
+     * @param event The LogEvent.
+     */
+    public void append(LogEvent event) {
+        try {
+            manager.send(getLayout().formatAs(event));
+        } catch (Exception ex) {
+            throw new AppenderRuntimeException(ex);
+        }
+    }
+
+    @PluginFactory
+    public static JMSQueueAppender createAppender(@PluginAttr("factoryName") String factoryName,
+                                                  @PluginAttr("providerURL") String providerURL,
+                                                  @PluginAttr("urlPkgPrefixes") String urlPkgPrefixes,
+                                                  @PluginAttr("securityPrincipalName") String securityPrincipalName,
+                                                  @PluginAttr("securityCredentials") String securityCredentials,
+                                                  @PluginAttr("factoryBindingName") String factoryBindingName,
+                                                  @PluginAttr("queueBindingName") String queueBindingName,
+                                                  @PluginAttr("userName") String userName,
+                                                  @PluginAttr("password") String password,
+                                                  @PluginElement("layout") Layout layout,
+                                                  @PluginElement("filters") Filters filters) {
+
+        String name = "JMSQueue" + factoryBindingName + "." + queueBindingName;
+        JMSQueueManager manager = JMSQueueManager.getJMSQueueManager(factoryName, providerURL, urlPkgPrefixes,
+            securityPrincipalName, securityCredentials, factoryBindingName, queueBindingName, userName, password);
+        if (manager == null) {
+            return null;
+        }
+        if (layout == null) {
+            layout = SerializedLayout.createLayout();
+        }
+        return new JMSQueueAppender(name, filters, layout, manager);
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/JMSTopicAppender.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/JMSTopicAppender.java
new file mode 100644
index 0000000..d9c73d1
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/appender/JMSTopicAppender.java
@@ -0,0 +1,79 @@
+/*
+ * 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 org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttr;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.filter.Filters;
+import org.apache.logging.log4j.core.layout.SerializedLayout;
+import org.apache.logging.log4j.core.net.JMSTopicManager;
+
+/**
+ *
+ */
+@Plugin(name="JMSTopic",type="Core",elementType="appender",printObject=true)
+public class JMSTopicAppender extends AppenderBase {
+
+    private final JMSTopicManager manager;
+
+    public JMSTopicAppender(String name, Filters filters, Layout layout, JMSTopicManager manager) {
+        super(name, filters, layout);
+        this.manager = manager;
+    }
+
+    /**
+     * Actual writing occurs here.
+     * <p/>
+     * @param event The LogEvent.
+     */
+    public void append(LogEvent event) {
+        try {
+            manager.send(getLayout().formatAs(event));
+        } catch (Exception ex) {
+            throw new AppenderRuntimeException(ex);
+        }
+    }
+
+    @PluginFactory
+    public static JMSTopicAppender createAppender(@PluginAttr("factoryName") String factoryName,
+                                                  @PluginAttr("providerURL") String providerURL,
+                                                  @PluginAttr("urlPkgPrefixes") String urlPkgPrefixes,
+                                                  @PluginAttr("securityPrincipalName") String securityPrincipalName,
+                                                  @PluginAttr("securityCredentials") String securityCredentials,
+                                                  @PluginAttr("factoryBindingName") String factoryBindingName,
+                                                  @PluginAttr("topicBindingName") String topicBindingName,
+                                                  @PluginAttr("userName") String userName,
+                                                  @PluginAttr("password") String password,
+                                                  @PluginElement("layout") Layout layout,
+                                                  @PluginElement("filters") Filters filters) {
+
+        String name = "JMSTopic" + factoryBindingName + "." + topicBindingName;
+        JMSTopicManager manager = JMSTopicManager.getJMSTopicManager(factoryName, providerURL, urlPkgPrefixes,
+            securityPrincipalName, securityCredentials, factoryBindingName, topicBindingName, userName, password);
+        if (manager == null) {
+            return null;
+        }
+        if (layout == null) {
+            layout = SerializedLayout.createLayout();
+        }
+        return new JMSTopicAppender(name, filters, layout, manager);
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java
index 048ad8c..ed4443e 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java
@@ -44,10 +44,13 @@
         root.setLevel(level);
     }
 
-    public class BasicLayout implements Layout {
+    public class BasicLayout implements Layout<String> {
         public byte[] format(LogEvent event) {
-            String result = event.getMessage().getFormattedMessage() + "\n";
-            return result.getBytes();
+            return formatAs(event).getBytes();
+        }
+
+        public String formatAs(LogEvent event) {
+            return event.getMessage().getFormattedMessage() + "\n";
         }
 
         public byte[] getHeader() {
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
new file mode 100644
index 0000000..7a7cf2d
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
@@ -0,0 +1,44 @@
+/*
+ * 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.layout;
+
+import org.apache.logging.log4j.core.LogEvent;
+
+import java.nio.charset.Charset;
+
+/**
+ *
+ */
+public abstract class AbstractStringLayout extends LayoutBase<String> {
+
+    /**
+     * The charset of the formatted message.
+     */
+    private Charset charset;
+
+    protected AbstractStringLayout(Charset charset) {
+        this.charset = charset;
+    }
+
+    public byte[] format(LogEvent event) {
+        return formatAs(event).getBytes(charset);
+    }
+
+    protected Charset getCharset() {
+        return charset;
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/HTMLLayout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/HTMLLayout.java
index 45ae6d8..7166bd3 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/HTMLLayout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/HTMLLayout.java
@@ -40,7 +40,7 @@
  * non ASCII characters could result in corrupted log files.
  */
 @Plugin(name="HTMLLayout",type="Core",elementType="layout",printObject=true)
-public class HTMLLayout extends LayoutBase {
+public class HTMLLayout extends AbstractStringLayout {
 
     protected static final int BUF_SIZE = 256;
 
@@ -57,16 +57,14 @@
 
     protected final String contentType;
 
-    protected final Charset charset;
-
     public HTMLLayout(boolean locationInfo, String title, String contentType, Charset charset) {
+        super(charset);
         this.locationInfo = locationInfo;
         this.title = title;
         this.contentType = contentType;
-        this.charset = charset;
     }
 
-    public byte[] format(LogEvent event) {
+    public String formatAs(LogEvent event) {
         StringBuilder sbuf = new StringBuilder(BUF_SIZE);
 
         sbuf.append(LINE_SEP).append("<tr>").append(LINE_SEP);
@@ -138,7 +136,7 @@
             sbuf.append("</td></tr>").append(LINE_SEP);
         }
 
-        return sbuf.toString().getBytes(charset);
+        return sbuf.toString();
     }
 
     void appendThrowableAsHTML(Throwable throwable, StringBuilder sbuf) {
@@ -211,7 +209,7 @@
         }
         sbuf.append("<th>Message</th>").append(LINE_SEP);
         sbuf.append("</tr>").append(LINE_SEP);
-        return sbuf.toString().getBytes(charset);
+        return sbuf.toString().getBytes(getCharset());
     }
 
     /**
@@ -223,7 +221,7 @@
         sbuf.append("</table>").append(LINE_SEP);
         sbuf.append("<br>").append(LINE_SEP);
         sbuf.append("</body></html>");
-        return sbuf.toString().getBytes(charset);
+        return sbuf.toString().getBytes(getCharset());
     }
 
     @PluginFactory
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/LayoutBase.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/LayoutBase.java
index 61713ec..d5c9f34 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/LayoutBase.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/LayoutBase.java
@@ -20,10 +20,12 @@
 import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.internal.StatusLogger;
 
+import java.io.Serializable;
+
 /**
  *
  */
-public abstract class LayoutBase implements Layout {
+public abstract class LayoutBase<T extends Serializable> implements Layout<T> {
 
     protected byte[] header;
     protected byte[] footer;
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index 32fd2d9..3930af0 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -388,7 +388,7 @@
  * Engineering Approach", ISBN 0-387-97389-3.
  */
 @Plugin(name="PatternLayout",type="Core",elementType="layout",printObject=true)
-public class PatternLayout extends LayoutBase {
+public class PatternLayout extends AbstractStringLayout {
     /**
      * Default pattern string for log output. Currently set to the
      * string <b>"%m%n"</b> which just prints the application supplied
@@ -428,11 +428,6 @@
     private boolean handlesExceptions;
 
     /**
-     * The charset of the formatted message.
-     */
-    private Charset charset;
-
-    /**
      * Constructs a EnhancedPatternLayout using the DEFAULT_LAYOUT_PATTERN.
      * <p/>
      * The default pattern just produces the application supplied message.
@@ -456,9 +451,8 @@
      * @param pattern conversion pattern.
      */
     public PatternLayout(final String pattern, final Charset charset) {
-
+        super(charset);
         this.conversionPattern = pattern;
-        this.charset = charset;
         PatternParser parser = createPatternParser();
         converters = parser.parse((pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern);
         handlesExceptions = parser.handlesExceptions();
@@ -487,12 +481,12 @@
      *
      * @param event logging event to be formatted.
      */
-    public byte[] format(final LogEvent event) {
+    public String formatAs(final LogEvent event) {
         StringBuilder buf = new StringBuilder();
         for (PatternConverter c : converters) {
             c.format(event, buf);
         }
-        return buf.toString().getBytes(charset);
+        return buf.toString();
     }
 
     private PatternParser createPatternParser() {
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/RFC5424Layout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/RFC5424Layout.java
index 496e58f..f591254 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/RFC5424Layout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/RFC5424Layout.java
@@ -45,14 +45,13 @@
  *
  */
 @Plugin(name="RFC5424Layout",type="Core",elementType="layout",printObject=true)
-public class RFC5424Layout extends LayoutBase {
+public class RFC5424Layout extends AbstractStringLayout {
 
     private final Facility facility;
     private final String defaultId;
     private final Integer enterpriseNumber;
     private final boolean includeMDC;
     private final String mdcId;
-    private final Charset charset;
     private final String localHostName;
     private final String appName;
     private final String messageId;
@@ -75,6 +74,7 @@
 
     public RFC5424Layout(Facility facility, String id, int ein, boolean includeMDC, boolean includeNL, String mdcId,
                          String appName, String messageId, String excludes, String includes, Charset charset) {
+        super(charset);
         this.facility = facility;
         this.defaultId = id == null ? DEFAULT_ID : id;
         this.enterpriseNumber = ein;
@@ -83,7 +83,6 @@
         this.mdcId = mdcId;
         this.appName = appName;
         this.messageId = messageId;
-        this.charset = charset;
         this.localHostName = getLocalHostname();
         ListChecker c = null;
         if (excludes != null) {
@@ -123,7 +122,7 @@
     /**
      * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification.
      */
-    public byte[] format(final LogEvent event) {
+    public String formatAs(final LogEvent event) {
         Message msg = event.getMessage();
         boolean isStructured = msg instanceof StructuredDataMessage;
         StringBuilder buf = new StringBuilder();
@@ -176,7 +175,7 @@
         if (includeNewLine) {
             buf.append("\n");
         }
-        return buf.toString().getBytes(charset);
+        return buf.toString();
     }
 
     protected String getProcId() {
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java
index 70bf498..59ee3d8 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java
@@ -42,7 +42,7 @@
  *
  */
 @Plugin(name="SerializedLayout",type="Core",elementType="layout",printObject=true)
-public class SerializedLayout extends LayoutBase {
+public class SerializedLayout extends LayoutBase<LogEvent> {
     private static int count = 0;
 
     private static byte[] header;
@@ -75,6 +75,10 @@
         return baos.toByteArray();
     }
 
+    public LogEvent formatAs(final LogEvent event) {
+        return event;
+    }
+
     @PluginFactory
     public static SerializedLayout createLayout() {
 
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
index d13803f..75b86e9 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
@@ -35,9 +35,8 @@
  *
  */
 @Plugin(name="SyslogLayout",type="Core",elementType="layout",printObject=true)
-public class SyslogLayout extends LayoutBase {
+public class SyslogLayout extends AbstractStringLayout {
 
-    private final Charset charset;
     private final Facility facility;
     private final boolean includeNewLine;
 
@@ -52,15 +51,15 @@
 
 
     public SyslogLayout(Facility facility, boolean includeNL, Charset c) {
+        super(c);
         this.facility = facility;
         this.includeNewLine = includeNL;
-        this.charset = c;
     }
 
     /**
      * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the log4j.dtd.
      */
-    public byte[] format(final LogEvent event) {
+    public String formatAs(final LogEvent event) {
         StringBuilder buf = new StringBuilder();
 
         buf.append("<");
@@ -74,7 +73,7 @@
         if (includeNewLine) {
             buf.append("\n");
         }
-        return buf.toString().getBytes(charset);
+        return buf.toString();
     }
 
     /**
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java
index 169d1cb..2a04a13 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java
@@ -69,7 +69,7 @@
  * log files.
  */
 @Plugin(name="XMLLayout",type="Core",elementType="layout",printObject=true)
-public class XMLLayout extends LayoutBase {
+public class XMLLayout extends AbstractStringLayout {
 
     private final boolean locationInfo;
     private final boolean properties;
@@ -77,11 +77,9 @@
 
     protected static final int DEFAULT_SIZE = 256;
 
-    protected final Charset charset;
-
     public XMLLayout(boolean locationInfo, boolean properties, boolean complete, Charset charset) {
+        super(charset);
         this.locationInfo = locationInfo;
-        this.charset = charset;
         this.properties = properties;
         this.complete = complete;
     }
@@ -89,7 +87,7 @@
     /**
      * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the log4j.dtd.
      */
-    public byte[] format(final LogEvent event) {
+    public String formatAs(final LogEvent event) {
         StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
 
         // We yield to the \r\n heresy.
@@ -158,7 +156,7 @@
 
         buf.append("</log4j:event>\r\n\r\n");
 
-        return buf.toString().getBytes(charset);
+        return buf.toString();
     }
 
     /**
@@ -172,7 +170,7 @@
         StringBuilder sbuf = new StringBuilder();
         sbuf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
         sbuf.append("<log4j:eventSet xmlns:log4j=\"http://jakarta.apache.org/log4j/\">\r\n");
-        return sbuf.toString().getBytes(charset);
+        return sbuf.toString().getBytes(getCharset());
     }
 
 
@@ -186,7 +184,7 @@
         }
         StringBuilder sbuf = new StringBuilder();
         sbuf.append("</log4j:eventSet>\r\n");
-        return sbuf.toString().getBytes(charset);
+        return sbuf.toString().getBytes(getCharset());
     }
 
     List<String> getThrowableString(Throwable throwable) {
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/AbstractJMSManager.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/AbstractJMSManager.java
new file mode 100644
index 0000000..f076f06
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/AbstractJMSManager.java
@@ -0,0 +1,108 @@
+/*
+ * 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.net;
+
+import org.apache.logging.log4j.core.appender.AbstractManager;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import java.io.Serializable;
+import java.util.Properties;
+
+/**
+ *
+ */
+public abstract class AbstractJMSManager extends AbstractManager {
+
+    public AbstractJMSManager(String name) {
+        super(name);
+    }
+
+    protected static Context createContext(String factoryName, String providerURL, String urlPkgPrefixes,
+                                           String securityPrincipalName, String securityCredentials)
+        throws NamingException {
+
+        Properties props = getEnvironment(factoryName, providerURL, urlPkgPrefixes, securityPrincipalName,
+                                          securityCredentials);
+        return new InitialContext(props);
+    }
+
+    protected static Object lookup(Context ctx, String name) throws NamingException {
+        try {
+            return ctx.lookup(name);
+        } catch(NameNotFoundException e) {
+            logger.error("Could not find name [" + name + "].");
+            throw e;
+        }
+    }
+
+    protected static Properties getEnvironment(String factoryName, String providerURL, String urlPkgPrefixes,
+                                               String securityPrincipalName, String securityCredentials) {
+        Properties props = new Properties();
+        if (factoryName != null) {
+            props.put(Context.INITIAL_CONTEXT_FACTORY, factoryName);
+            if (providerURL != null) {
+                props.put(Context.PROVIDER_URL, providerURL);
+            } else {
+                logger.warn("The InitalContext factory name has been provided without a ProviderURL. " +
+                    "This is likely to cause problems");
+            }
+            if (urlPkgPrefixes != null) {
+                props.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
+            }
+	          if (securityPrincipalName != null) {
+	              props.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
+	              if (securityCredentials != null) {
+	                  props.put(Context.SECURITY_CREDENTIALS, securityCredentials);
+	              } else {
+	                  logger.warn("SecurityPrincipalName has been set without SecurityCredentials. " +
+                        "This is likely to cause problems.");
+	              }
+	          }
+            return props;
+        }
+        return null;
+    }
+
+    public abstract void send(Serializable Object) throws Exception;
+
+
+    public synchronized void send(Serializable object, Session session, MessageProducer producer) throws Exception {
+        try {
+            Message msg;
+            if (object instanceof String) {
+                msg = session.createTextMessage();
+                ((TextMessage) msg).setText((String) object);
+            } else {
+                msg = session.createObjectMessage();
+                ((ObjectMessage) msg).setObject(object);
+            }
+            producer.send(msg);
+        } catch (JMSException ex) {
+            logger.error("Could not publish message via JMS " + getName());
+            throw ex;
+        }
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/AbstractJMSReceiver.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/AbstractJMSReceiver.java
new file mode 100644
index 0000000..9973bdc
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/AbstractJMSReceiver.java
@@ -0,0 +1,64 @@
+/*
+ * 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.net;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.AbstractServer;
+import org.apache.logging.log4j.core.LogEvent;
+
+import javax.jms.JMSException;
+import javax.jms.ObjectMessage;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+
+/**
+ *
+ */
+public abstract class AbstractJMSReceiver extends AbstractServer implements javax.jms.MessageListener {
+
+
+    protected Logger logger = LogManager.getLogger(this.getClass().getName());
+
+    public void onMessage(javax.jms.Message message) {
+
+        try {
+            if (message instanceof ObjectMessage) {
+                ObjectMessage objectMessage = (ObjectMessage) message;
+                log((LogEvent) objectMessage.getObject());
+            } else {
+                logger.warn("Received message is of type " + message.getJMSType()
+                    + ", was expecting ObjectMessage.");
+            }
+        } catch (JMSException jmse) {
+            logger.error("Exception thrown while processing incoming message.",
+                jmse);
+        }
+    }
+
+
+    protected Object lookup(Context ctx, String name) throws NamingException {
+        try {
+            return ctx.lookup(name);
+        } catch (NameNotFoundException e) {
+            logger.error("Could not find name [" + name + "].");
+            throw e;
+        }
+    }
+
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSQueueManager.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSQueueManager.java
new file mode 100644
index 0000000..d39cc31
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSQueueManager.java
@@ -0,0 +1,144 @@
+/*
+ * 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.net;
+
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.QueueConnection;
+import javax.jms.QueueConnectionFactory;
+import javax.jms.QueueSender;
+import javax.jms.QueueSession;
+import javax.jms.Session;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import java.io.Serializable;
+
+/**
+ *
+ */
+public class JMSQueueManager extends AbstractJMSManager {
+
+    private QueueConnection queueConnection;
+    private QueueSession queueSession;
+    private QueueSender queueSender;
+
+
+    private static ManagerFactory factory = new JMSTopicManagerFactory();
+
+    public static JMSQueueManager getJMSQueueManager(String factoryName, String providerURL, String urlPkgPrefixes,
+                                                     String securityPrincipalName, String securityCredentials,
+                                                     String factoryBindingName, String queueBindingName,
+                                                     String userName, String password) {
+
+        if (factoryBindingName == null) {
+            logger.error("No factory name provided for JMSQueueManager");
+            return null;
+        }
+        if (queueBindingName == null) {
+            logger.error("No topic name provided for JMSQueueManager");
+            return null;
+        }
+
+        String name = "JMSQueue:" + factoryBindingName + "." + queueBindingName;
+        return (JMSQueueManager) getManager(name, factory, new FactoryData(factoryName, providerURL, urlPkgPrefixes,
+            securityPrincipalName, securityCredentials, factoryBindingName, queueBindingName, userName, password));
+    }
+
+    public JMSQueueManager(String name, QueueConnection conn, QueueSession sess, QueueSender sender) {
+        super(name);
+        this.queueConnection = conn;
+        this.queueSession = sess;
+        this.queueSender = sender;
+    }
+
+    @Override
+    public void send(Serializable object) throws Exception {
+        super.send(object, queueSession, queueSender);
+    }
+
+    @Override
+    public void releaseSub() {
+        try {
+            if (queueSession != null) {
+                queueSession.close();
+            }
+            if (queueConnection != null) {
+                queueConnection.close();
+            }
+        } catch (JMSException ex) {
+            logger.error("Error closing " + getName(), ex);
+        }
+    }
+
+
+    private static class FactoryData {
+        String factoryName;
+        String providerURL;
+        String urlPkgPrefixes;
+        String securityPrincipalName;
+        String securityCredentials;
+        String factoryBindingName;
+        String queueBindingName;
+        String userName;
+        String password;
+
+        public FactoryData(String factoryName, String providerURL, String urlPkgPrefixes, String securityPrincipalName,
+                           String securityCredentials, String factoryBindingName, String queueBindingName,
+                           String userName, String password) {
+            this.factoryName = factoryName;
+            this.providerURL = providerURL;
+            this.urlPkgPrefixes = urlPkgPrefixes;
+            this.securityPrincipalName = securityPrincipalName;
+            this.securityCredentials = securityCredentials;
+            this.factoryBindingName = factoryBindingName;
+            this.queueBindingName = queueBindingName;
+            this.userName = userName;
+            this.password = password;
+        }
+    }
+
+    private static class JMSTopicManagerFactory implements ManagerFactory<JMSQueueManager, FactoryData> {
+
+        public JMSQueueManager createManager(String name, FactoryData data) {
+            try {
+                Context ctx = createContext(data.factoryName, data.providerURL, data.urlPkgPrefixes,
+                                            data.securityPrincipalName, data.securityCredentials);
+                QueueConnectionFactory factory = (QueueConnectionFactory) lookup(ctx, data.factoryBindingName);
+                QueueConnection conn;
+                if (data.userName != null) {
+                    conn = factory.createQueueConnection(data.userName, data.password);
+                } else {
+                    conn = factory.createQueueConnection();
+                }
+                QueueSession sess = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
+                Queue queue = (Queue) lookup(ctx, data.queueBindingName);
+                QueueSender sender = sess.createSender(queue);
+                conn.start();
+                return new JMSQueueManager(name, conn, sess, sender);
+
+            } catch (NamingException ex) {
+
+            } catch (JMSException jmsex) {
+
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSQueueReceiver.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSQueueReceiver.java
new file mode 100644
index 0000000..fd1dd51
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSQueueReceiver.java
@@ -0,0 +1,89 @@
+/*
+ * 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.net;
+
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.QueueConnection;
+import javax.jms.QueueConnectionFactory;
+import javax.jms.QueueReceiver;
+import javax.jms.QueueSession;
+import javax.jms.Session;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ *
+ */
+public class JMSQueueReceiver extends AbstractJMSReceiver {
+
+    static public void main(String[] args) throws Exception {
+        if (args.length != 4) {
+            usage("Wrong number of arguments.");
+        }
+
+        String qcfBindingName = args[0];
+        String queueBindingName = args[1];
+        String username = args[2];
+        String password = args[3];
+
+        new JMSQueueReceiver(qcfBindingName, queueBindingName, username, password);
+
+        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+        // Loop until the word "exit" is typed
+        System.out.println("Type \"exit\" to quit JMSQueueReceiver.");
+        while (true) {
+            String s = stdin.readLine();
+            if (s.equalsIgnoreCase("exit")) {
+                System.out.println("Exiting. Kill the application if it does not exit "
+                    + "due to daemon threads.");
+                return;
+            }
+        }
+    }
+
+    public JMSQueueReceiver(String qcfBindingName, String queueBindingName, String username, String password) {
+
+        try {
+            Context ctx = new InitialContext();
+            QueueConnectionFactory queueConnectionFactory;
+            queueConnectionFactory = (QueueConnectionFactory) lookup(ctx,qcfBindingName);
+            QueueConnection queueConnection = queueConnectionFactory.createQueueConnection(username, password);
+            queueConnection.start();
+            QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
+            Queue queue = (Queue) ctx.lookup(queueBindingName);
+            QueueReceiver queueReceiver = queueSession.createReceiver(queue);
+            queueReceiver.setMessageListener(this);
+        } catch (JMSException e) {
+            logger.error("Could not read JMS message.", e);
+        } catch (NamingException e) {
+            logger.error("Could not read JMS message.", e);
+        } catch (RuntimeException e) {
+            logger.error("Could not read JMS message.", e);
+        }
+    }
+
+    static void usage(String msg) {
+        System.err.println(msg);
+        System.err.println("Usage: java " + JMSQueueReceiver.class.getName()
+            + " QueueConnectionFactoryBindingName QueueBindingName username password");
+        System.exit(1);
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSTopicManager.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSTopicManager.java
new file mode 100644
index 0000000..c602475
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSTopicManager.java
@@ -0,0 +1,147 @@
+/*
+ * 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.net;
+
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import java.io.Serializable;
+
+/**
+ *
+ */
+public class JMSTopicManager extends AbstractJMSManager {
+
+    private TopicConnection topicConnection;
+    private TopicSession topicSession;
+    private TopicPublisher topicPublisher;
+
+
+    private static ManagerFactory factory = new JMSTopicManagerFactory();
+
+    public static JMSTopicManager getJMSTopicManager(String factoryName, String providerURL, String urlPkgPrefixes,
+                                                     String securityPrincipalName, String securityCredentials,
+                                                     String factoryBindingName, String topicBindingName,
+                                                     String userName, String password) {
+
+        if (factoryBindingName == null) {
+            logger.error("No factory name provided for JMSTopicManager");
+            return null;
+        }
+        if (topicBindingName == null) {
+            logger.error("No topic name provided for JMSTopicManager");
+            return null;
+        }
+
+        String name = "JMSTopic:" + factoryBindingName + "." + topicBindingName;
+        return (JMSTopicManager) getManager(name, factory, new FactoryData(factoryName, providerURL, urlPkgPrefixes,
+            securityPrincipalName, securityCredentials, factoryBindingName, topicBindingName, userName, password));
+    }
+
+    public JMSTopicManager(String name, TopicConnection conn, TopicSession sess, TopicPublisher pub) {
+        super(name);
+        this.topicConnection = conn;
+        this.topicSession = sess;
+        this.topicPublisher = pub;
+    }
+
+    @Override
+    public void send(Serializable object) throws Exception {
+        super.send(object, topicSession, topicPublisher);
+    }
+
+    @Override
+    public void releaseSub() {
+        try {
+            if (topicSession != null) {
+                topicSession.close();
+            }
+            if (topicConnection != null) {
+                topicConnection.close();
+            }
+        } catch (JMSException ex) {
+            logger.error("Error closing " + getName(), ex);
+        }
+    }
+
+
+    private static class FactoryData {
+        String factoryName;
+        String providerURL;
+        String urlPkgPrefixes;
+        String securityPrincipalName;
+        String securityCredentials;
+        String factoryBindingName;
+        String topicBindingName;
+        String userName;
+        String password;
+
+        public FactoryData(String factoryName, String providerURL, String urlPkgPrefixes, String securityPrincipalName,
+                           String securityCredentials, String factoryBindingName, String topicBindingName,
+                           String userName, String password) {
+            this.factoryName = factoryName;
+            this.providerURL = providerURL;
+            this.urlPkgPrefixes = urlPkgPrefixes;
+            this.securityPrincipalName = securityPrincipalName;
+            this.securityCredentials = securityCredentials;
+            this.factoryBindingName = factoryBindingName;
+            this.topicBindingName = topicBindingName;
+            this.userName = userName;
+            this.password = password;
+        }
+    }
+
+    private static class JMSTopicManagerFactory implements ManagerFactory<JMSTopicManager, FactoryData> {
+
+        public JMSTopicManager createManager(String name, FactoryData data) {
+            try {
+                Context ctx = createContext(data.factoryName, data.providerURL, data.urlPkgPrefixes,
+                                            data.securityPrincipalName, data.securityCredentials);
+                TopicConnectionFactory factory = (TopicConnectionFactory) lookup(ctx, data.factoryBindingName);
+                TopicConnection conn;
+                if (data.userName != null) {
+                    conn = factory.createTopicConnection(data.userName, data.password);
+                } else {
+                    conn = factory.createTopicConnection();
+                }
+                TopicSession sess = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+                Topic topic = (Topic) lookup(ctx, data.topicBindingName);
+                TopicPublisher pub = sess.createPublisher(topic);
+                conn.start();
+                return new JMSTopicManager(name, conn, sess, pub);
+            } catch (NamingException ex) {
+                logger.error("Bad Name " + data.topicBindingName, ex);
+            } catch (JMSException jmsex) {
+                logger.error("Unable to create publisher ", jmsex);
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSTopicReceiver.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSTopicReceiver.java
new file mode 100644
index 0000000..42ddd5f
--- /dev/null
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/net/JMSTopicReceiver.java
@@ -0,0 +1,95 @@
+/*
+ * 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.net;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.AbstractServer;
+import org.apache.logging.log4j.core.LogEvent;
+
+import javax.jms.JMSException;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ *
+ */
+public class JMSTopicReceiver extends AbstractJMSReceiver {
+
+    static public void main(String[] args) throws Exception {
+        if (args.length != 4) {
+            usage("Wrong number of arguments.");
+        }
+
+        String tcfBindingName = args[0];
+        String topicBindingName = args[1];
+        String username = args[2];
+        String password = args[3];
+
+        new JMSTopicReceiver(tcfBindingName, topicBindingName, username, password);
+
+        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+        // Loop until the word "exit" is typed
+        System.out.println("Type \"exit\" to quit JMSTopicReceiver.");
+        while (true) {
+            String s = stdin.readLine();
+            if (s.equalsIgnoreCase("exit")) {
+                System.out.println("Exiting. Kill the application if it does not exit "
+                    + "due to daemon threads.");
+                return;
+            }
+        }
+    }
+
+    public JMSTopicReceiver(String tcfBindingName, String topicBindingName, String username, String password) {
+        try {
+            Context ctx = new InitialContext();
+            TopicConnectionFactory topicConnectionFactory;
+            topicConnectionFactory = (TopicConnectionFactory) lookup(ctx, tcfBindingName);
+            TopicConnection topicConnection = topicConnectionFactory.createTopicConnection(username, password);
+            topicConnection.start();
+            TopicSession topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+            Topic topic = (Topic) ctx.lookup(topicBindingName);
+            TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
+            topicSubscriber.setMessageListener(this);
+        } catch (JMSException e) {
+            logger.error("Could not read JMS message.", e);
+        } catch (NamingException e) {
+            logger.error("Could not read JMS message.", e);
+        } catch (RuntimeException e) {
+            logger.error("Could not read JMS message.", e);
+        }
+    }
+
+    static void usage(String msg) {
+        System.err.println(msg);
+        System.err.println("Usage: java " + JMSTopicReceiver.class.getName()
+            + " TopicConnectionFactoryBindingName TopicBindingName username password");
+        System.exit(1);
+    }
+}
diff --git a/log4j2-core/src/test/java/org/apache/logging/log4j/core/net/JMSQueueTest.java b/log4j2-core/src/test/java/org/apache/logging/log4j/core/net/JMSQueueTest.java
new file mode 100644
index 0000000..400ed28
--- /dev/null
+++ b/log4j2-core/src/test/java/org/apache/logging/log4j/core/net/JMSQueueTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.net;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.appender.JMSQueueAppender;
+import org.apache.logging.log4j.core.appender.JMSTopicAppender;
+import org.apache.logging.log4j.core.appender.ListAppender;
+import org.apache.logging.log4j.core.filter.FilterBase;
+import org.apache.logging.log4j.core.filter.Filters;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.internal.StatusConsoleListener;
+import org.apache.logging.log4j.internal.StatusLogger;
+import org.mockejb.jndi.*;
+import org.mockejb.jms.*;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class JMSQueueTest {
+
+    private static final String FACTORY_NAME = "TestQueueConnectionFactory";
+    private static final String QUEUE_NAME = "TestQueue";
+
+    private static Context context;
+    private static AbstractJMSReceiver receiver;
+
+    LoggerContext ctx = (LoggerContext) LogManager.getContext();
+    Logger root = ctx.getLogger("JMSQueueTest");
+
+    @BeforeClass
+    public static void setupClass() throws Exception {
+        // MockContextFactory becomes the primary JNDI provider
+        StatusConsoleListener l = new StatusConsoleListener(Level.ERROR);
+        StatusLogger.getLogger().registerListener(l);
+        MockContextFactory.setAsInitial();
+        context = new InitialContext();
+        context.rebind(FACTORY_NAME, new QueueConnectionFactoryImpl() );
+        context.rebind(QUEUE_NAME, new MockQueue(QUEUE_NAME));
+        ((LoggerContext) LogManager.getContext()).reconfigure();
+        receiver = new JMSQueueReceiver(FACTORY_NAME, QUEUE_NAME, null, null);
+    }
+
+    @AfterClass
+    public static void cleanupClass() {
+        StatusLogger.getLogger().reset();
+    }
+
+    @After
+    public void teardown() {
+        Map<String,Appender> map = root.getAppenders();
+        for (Map.Entry<String, Appender> entry : map.entrySet()) {
+            Appender app = entry.getValue();
+            root.removeAppender(app);
+            app.stop();
+        }
+    }
+
+    @Test
+    public void testServer() throws Exception {
+        Filter clientFilter = new MessageFilter(Filter.Result.NEUTRAL, Filter.Result.DENY);
+        Filter serverFilter = new MessageFilter(Filter.Result.DENY, Filter.Result.NEUTRAL);
+        Filters clientFilters = Filters.createFilters(new Filter[] {clientFilter});
+        JMSQueueAppender appender = JMSQueueAppender.createAppender(null, null, null, null, null, FACTORY_NAME,
+                QUEUE_NAME, null, null, null, clientFilters);
+        appender.start();
+        Filters serverFilters = Filters.createFilters(new Filter[] {serverFilter});
+        ListAppender listApp = new ListAppender("Events", serverFilters, null, false, false);
+        listApp.start();
+        PatternLayout layout = new PatternLayout("%m %ex%n");
+        ConsoleAppender console = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console");
+        console.start();
+        Logger serverLogger = ctx.getLogger(JMSTopicReceiver.class.getName());
+        serverLogger.addAppender(console);
+        serverLogger.setAdditive(false);
+
+
+        // set appender on root and set level to debug
+        root.addAppender(listApp);
+        root.addAppender(appender);
+        root.setAdditive(false);
+        root.setLevel(Level.DEBUG);
+        root.debug("This is a test message");
+        Thread.sleep(100);
+        List<LogEvent> events = listApp.getEvents();
+        assertNotNull("No event retrieved", events);
+        assertTrue("No events retrieved", events.size() > 0);
+        assertTrue("Incorrect event", events.get(0).getMessage().getFormattedMessage().equals("This is a test message"));
+    }
+
+    private class MessageFilter extends FilterBase {
+        public MessageFilter(Result onMatch, Result onMismatch) {
+            super(onMatch, onMismatch);
+        }
+
+        public Result filter(LogEvent event) {
+            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+            for (StackTraceElement element : stackTrace) {
+                if (element.getMethodName().equals("onMessage")) {
+                    return onMatch;
+                } else if (element.getMethodName().equals("testServer")) {
+                    return onMismatch;
+                }
+            }
+            return onMismatch;
+        }
+    }
+}
diff --git a/log4j2-core/src/test/java/org/apache/logging/log4j/core/net/JMSTopicTest.java b/log4j2-core/src/test/java/org/apache/logging/log4j/core/net/JMSTopicTest.java
new file mode 100644
index 0000000..25c46b2
--- /dev/null
+++ b/log4j2-core/src/test/java/org/apache/logging/log4j/core/net/JMSTopicTest.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.logging.log4j.core.net;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.appender.JMSTopicAppender;
+import org.apache.logging.log4j.core.appender.ListAppender;
+import org.apache.logging.log4j.core.filter.FilterBase;
+import org.apache.logging.log4j.core.filter.Filters;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.internal.StatusConsoleListener;
+import org.apache.logging.log4j.internal.StatusLogger;
+import org.mockejb.jndi.*;
+import org.mockejb.jms.*;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class JMSTopicTest {
+
+    private static final String FACTORY_NAME = "TestTopicConnectionFactory";
+    private static final String TOPIC_NAME = "TestTopic";
+
+    private static Context context;
+    private static AbstractJMSReceiver receiver;
+
+    LoggerContext ctx = (LoggerContext) LogManager.getContext();
+    Logger root = ctx.getLogger("JMSTopicTest");
+
+    @BeforeClass
+    public static void setupClass() throws Exception {
+        // MockContextFactory becomes the primary JNDI provider
+        StatusConsoleListener l = new StatusConsoleListener(Level.ERROR);
+        StatusLogger.getLogger().registerListener(l);
+        MockContextFactory.setAsInitial();
+        context = new InitialContext();
+        context.rebind(FACTORY_NAME, new TopicConnectionFactoryImpl() );
+        context.rebind(TOPIC_NAME, new MockTopic(TOPIC_NAME) );
+        ((LoggerContext) LogManager.getContext()).reconfigure();
+        receiver = new JMSTopicReceiver(FACTORY_NAME, TOPIC_NAME, null, null);
+    }
+
+    @AfterClass
+    public static void cleanupClass() {
+        StatusLogger.getLogger().reset();
+    }
+
+    @After
+    public void teardown() {
+        Map<String,Appender> map = root.getAppenders();
+        for (Map.Entry<String, Appender> entry : map.entrySet()) {
+            Appender app = entry.getValue();
+            root.removeAppender(app);
+            app.stop();
+        }
+    }
+
+    @Test
+    public void testServer() throws Exception {
+        Filter clientFilter = new MessageFilter(Filter.Result.NEUTRAL, Filter.Result.DENY);
+        Filter serverFilter = new MessageFilter(Filter.Result.DENY, Filter.Result.NEUTRAL);
+        Filters clientFilters = Filters.createFilters(new Filter[] {clientFilter});
+        JMSTopicAppender appender = JMSTopicAppender.createAppender(null, null, null, null, null, FACTORY_NAME,
+                TOPIC_NAME, null, null, null, clientFilters);
+        appender.start();
+        Filters serverFilters = Filters.createFilters(new Filter[] {serverFilter});
+        ListAppender listApp = new ListAppender("Events", serverFilters, null, false, false);
+        listApp.start();
+        PatternLayout layout = new PatternLayout("%m %ex%n");
+        ConsoleAppender console = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console");
+        console.start();
+        Logger serverLogger = ctx.getLogger(JMSTopicReceiver.class.getName());
+        serverLogger.addAppender(console);
+        serverLogger.setAdditive(false);
+
+
+        // set appender on root and set level to debug
+        root.addAppender(listApp);
+        root.addAppender(appender);
+        root.setAdditive(false);
+        root.setLevel(Level.DEBUG);
+        root.debug("This is a test message");
+        Thread.sleep(100);
+        List<LogEvent> events = listApp.getEvents();
+        assertNotNull("No event retrieved", events);
+        assertTrue("No events retrieved", events.size() > 0);
+        assertTrue("Incorrect event", events.get(0).getMessage().getFormattedMessage().equals("This is a test message"));
+    }
+
+    private class MessageFilter extends FilterBase {
+        public MessageFilter(Result onMatch, Result onMismatch) {
+            super(onMatch, onMismatch);
+        }
+
+        public Result filter(LogEvent event) {
+            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+            for (StackTraceElement element : stackTrace) {
+                if (element.getMethodName().equals("onMessage")) {
+                    return onMatch;
+                } else if (element.getMethodName().equals("testServer")) {
+                    return onMismatch;
+                }
+            }
+            return onMismatch;
+        }
+    }
+}