diff --git a/contrib/java/build.xml b/contrib/java/build.xml
new file mode 100644
--- /dev/null
+++ b/contrib/java/build.xml
@@ -0,0 +1,38 @@
+<project name="hslogger4j" default="dist" basedir=".">
+    <description>
+        simple example build file
+    </description>
+  <!-- set global properties for this build -->
+  <property name="src"   location="."/>
+  <property name="build" location="build"/>
+  <property name="dist"  location="dist"/>
+
+  <target name="init">
+    <!-- Create the time stamp -->
+    <tstamp/>
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${build}"/>
+  </target>
+
+  <target name="compile" depends="init"
+        description="compile the source " >
+    <!-- Compile the java code from ${src} into ${build} -->
+    <javac srcdir="${src}" destdir="${build}"/>
+  </target>
+
+  <target name="dist" depends="compile"
+        description="generate the distribution" >
+    <!-- Create the distribution directory -->
+    <mkdir dir="${dist}"/>
+
+    <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
+    <jar jarfile="${dist}/hslogger4j-${DSTAMP}.jar" basedir="${build}"/>
+  </target>
+
+  <target name="clean"
+        description="clean up" >
+    <!-- Delete the ${build} and ${dist} directory trees -->
+    <delete dir="${build}"/>
+    <delete dir="${dist}"/>
+  </target>
+</project>
diff --git a/contrib/java/hslogger4j-plugins.xml b/contrib/java/hslogger4j-plugins.xml
new file mode 100644
--- /dev/null
+++ b/contrib/java/hslogger4j-plugins.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration >
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">
+
+  <plugin name="Hslogger4jSocketReceiver" 
+          class="org.apache.log4j.net.XMLSocketReceiver">
+          <param name="decoder" value="org.haskell.hslogger4j.XMLDecoder"/>
+    <param name="Port" value="4448"/>
+    <level value="DEBUG"/>
+  </plugin>
+
+  <plugin name="Hslogger4jFileReader" 
+          class="org.haskell.hslogger4j.LogFileXMLReceiver">
+    <param name="decoder" value="org.haskell.hslogger4j.XMLDecoder"/>
+  </plugin>
+
+  <root>
+    <level value="debug"/>
+  </root>
+
+</log4j:configuration>
+
diff --git a/contrib/java/hslogger4j.jar b/contrib/java/hslogger4j.jar
new file mode 100644
Binary files /dev/null and b/contrib/java/hslogger4j.jar differ
diff --git a/contrib/java/org/haskell/hslogger/HsloggerLevel.java b/contrib/java/org/haskell/hslogger/HsloggerLevel.java
new file mode 100644
--- /dev/null
+++ b/contrib/java/org/haskell/hslogger/HsloggerLevel.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.haskell.hslogger4j;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Level;
+
+/**
+ *  An extension of the Level class that provides support for java.util.logging 
+ * Levels.
+ *
+ *  @author Scott Deboy <sdeboy@apache.org>
+ *
+ */
+
+public class HsloggerLevel extends org.apache.log4j.Level {
+
+  public final static int EMERGENCY_INT = 51000; // FATAL
+  public final static int ALERT_INT     = 50000;
+  public final static int CRITICAL_INT  = 41000;
+  public final static int ERROR_INT     = 40000; // ERROR
+  public final static int WARNING_INT   = 30000; // WARN
+  public final static int NOTICE_INT    = 21000;
+  public static final int INFO_INT      = 20000; // INFO
+  public static final int DEBUG_INT     = 10000; // DEBUG
+  
+  public static final HsloggerLevel EMERGENCY = new HsloggerLevel(EMERGENCY_INT, "EMERGENCY", 0);
+  public static final HsloggerLevel ALERT     = new HsloggerLevel(ALERT_INT    , "ALERT"    , 1);      
+  public static final HsloggerLevel CRITICAL  = new HsloggerLevel(CRITICAL_INT , "CRITICAL" , 2);      
+  public static final HsloggerLevel ERROR     = new HsloggerLevel(ERROR_INT    , "ERROR"    , 3);
+  public static final HsloggerLevel WARNING   = new HsloggerLevel(WARNING_INT  , "WARNING"  , 4);
+  public static final HsloggerLevel NOTICE    = new HsloggerLevel(NOTICE_INT   , "NOTICE"   , 5);
+  public static final HsloggerLevel INFO      = new HsloggerLevel(INFO_INT     , "INFO"     , 6);
+  public static final HsloggerLevel DEBUG     = new HsloggerLevel(DEBUG_INT    , "DEBUG"    , 7);
+
+  protected HsloggerLevel(int level, String levelStr, int syslogEquivalent) {
+    super(level, levelStr, syslogEquivalent);
+  }
+
+  /**
+    Convert an integer passed as argument to a level. If the
+    conversion fails, then this method returns the specified default.
+  */
+  public static HsloggerLevel toLevel(int val, HsloggerLevel defaultLevel) {
+    switch (val) {
+    case EMERGENCY_INT:
+      return EMERGENCY;
+
+    case ALERT_INT:
+      return ALERT;
+
+    case CRITICAL_INT:
+      return CRITICAL;
+
+    case ERROR_INT:
+      return ERROR;
+
+    case WARNING_INT:
+      return WARNING;
+
+    case NOTICE_INT:
+      return NOTICE;
+      
+    case INFO_INT:
+      return INFO;
+
+    case DEBUG_INT:
+      return DEBUG;
+
+    default:
+      return defaultLevel;
+    }
+  }
+
+  public static Level toLevel(int val) {
+    return toLevel(val, CRITICAL);
+  }
+
+  public static List getAllPossibleLevels() {
+  	ArrayList list=new ArrayList();
+  	list.add(DEBUG);
+  	list.add(INFO);
+  	list.add(NOTICE);
+  	list.add(WARNING);
+  	list.add(ERROR);
+  	list.add(CRITICAL);
+  	list.add(ALERT);
+  	list.add(EMERGENCY);
+  	return list;
+  }
+
+  public static Level toLevel(String s) {
+  	return toLevel(s, Level.DEBUG);
+  }
+  
+  public static Level toLevel(String sArg, Level defaultLevel) {
+    if (sArg == null) {
+      return defaultLevel;
+    }
+
+    String s = sArg.toUpperCase();
+
+    if (s.equals("EMERGENCY")) {
+      return EMERGENCY;
+    }
+
+    if (s.equals("ALERT")) {
+      return ALERT;
+    }
+
+    if (s.equals("CRITICAL")) {
+      return CRITICAL;
+    }
+
+    if (s.equals("ERROR")) {
+      return ERROR;
+    }
+
+    if (s.equals("WARNING")) {
+      return WARNING;
+    }
+
+    if (s.equals("NOTICE")) {
+      return NOTICE;
+    }
+
+    if (s.equals("INFO")) {
+      return INFO;
+    }
+
+    if (s.equals("DEBUG")) {
+      return DEBUG;
+    }
+    return defaultLevel;
+  }
+
+}
+
diff --git a/contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java b/contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java
new file mode 100644
--- /dev/null
+++ b/contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation. Licensed 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.haskell.hslogger4j;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.log4j.xml.*;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.rule.ExpressionRule;
+import org.apache.log4j.rule.Rule;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * LogFileXMLReceiver will read an xml-formated log file and make the events in the log file
+ * available to the log4j framework.
+ * <p>
+ * This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging
+ * XMLFormatter (via the org.apache.log4j.spi.Decoder interface).
+ * <p>
+ * By default, log4j's XMLLayout is supported (no need to specify a decoder in that case).
+ * <p>
+ * To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param
+ * of org.apache.log4j.xml.UtilLoggingXMLDecoder.
+ * <p>
+ * Tailing -may- work, but not in all cases (try using a file:// URL). If a process has a log file
+ * open, the receiver may be able to read and tail the file. If the process closes the file and
+ * reopens the file, the receiver may not be able to continue tailing the file.
+ * <p>
+ * An expressionFilter may be specified. Only events passing the expression will be forwarded to the
+ * log4j framework.
+ * <p>
+ * Once the event has been "posted", it will be handled by the appenders currently configured in the
+ * LoggerRespository.
+ * 
+ * @author Scott Deboy <sdeboy@apache.org>
+ * @since 1.3
+ */
+
+public class LogFileXMLReceiver extends Receiver {
+    private String fileURL;
+    private Rule expressionRule;
+    private String filterExpression;
+    private String decoder = "org.apache.log4j.xml.XMLDecoder";
+    private boolean tailing = false;
+
+    private Decoder decoderInstance;
+    private Reader reader;
+    private static final String FILE_KEY = "file";
+    private String host;
+    private String path;
+    private boolean useCurrentThread;
+
+    /**
+     * Accessor
+     * 
+     * @return file URL
+     */
+    public String getFileURL() {
+        return fileURL;
+    }
+
+    /**
+     * Specify the URL of the XML-formatted file to process.
+     * 
+     * @param fileURL
+     */
+    public void setFileURL(String fileURL) {
+        this.fileURL = fileURL;
+    }
+
+    /**
+     * Accessor
+     * 
+     * @return
+     */
+    public String getDecoder() {
+        return decoder;
+    }
+
+    /**
+     * Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file.
+     * 
+     * @param _decoder
+     */
+    public void setDecoder(String _decoder) {
+        decoder = _decoder;
+    }
+
+    /**
+     * Accessor
+     * 
+     * @return filter expression
+     */
+    public String getFilterExpression() {
+        return filterExpression;
+    }
+
+    /**
+     * Accessor
+     * 
+     * @return tailing flag
+     */
+    public boolean isTailing() {
+        return tailing;
+    }
+
+    /**
+     * Set the 'tailing' flag - may only work on file:// URLs and may stop tailing if the writing
+     * process closes the file and reopens.
+     * 
+     * @param tailing
+     */
+    public void setTailing(boolean tailing) {
+        this.tailing = tailing;
+    }
+
+    /**
+     * Set the filter expression that will cause only events which pass the filter to be forwarded
+     * to the log4j framework.
+     * 
+     * @param filterExpression
+     */
+    public void setFilterExpression(String filterExpression) {
+        this.filterExpression = filterExpression;
+    }
+
+    private boolean passesExpression(LoggingEvent event) {
+        if (event != null) {
+            if (expressionRule != null) {
+                return (expressionRule.evaluate(event));
+            }
+        }
+        return true;
+    }
+
+    public static void main(String[] args) {
+        /*
+         * LogFileXMLReceiver test = new LogFileXMLReceiver();
+         * test.setFileURL("file:///c:/samplelog.xml"); test.setFilterExpression("level >= TRACE");
+         * test.activateOptions();
+         */
+    }
+
+    /**
+     * Close the receiver, release any resources that are accessing the file.
+     */
+    public void shutdown() {
+        try {
+            if (reader != null) {
+                reader.close();
+                reader = null;
+            }
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+
+    /**
+     * Process the file
+     */
+    public void activateOptions() {
+        Runnable runnable = new Runnable() {
+            public void run() {
+                try {
+                    URL url = new URL(fileURL);
+                    host = url.getHost();
+                    if (host != null && host.equals("")) {
+                        host = FILE_KEY;
+                    }
+                    path = url.getPath();
+                } catch (MalformedURLException e1) {
+                    // TODO Auto-generated catch block
+                    e1.printStackTrace();
+                }
+
+                try {
+                    if (filterExpression != null) {
+                        expressionRule = ExpressionRule.getRule(filterExpression);
+                    }
+                } catch (Exception e) {
+                    getLogger().warn("Invalid filter expression: " + filterExpression, e);
+                }
+
+                Class c;
+                try {
+                    c = Class.forName(decoder);
+                    Object o = c.newInstance();
+                    if (o instanceof Decoder) {
+                        decoderInstance = (Decoder) o;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (InstantiationException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (IllegalAccessException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+
+                try {
+                    reader = new InputStreamReader(new URL(getFileURL()).openStream());
+                    process(reader);
+                } catch (FileNotFoundException fnfe) {
+                    getLogger().info("file not available");
+                } catch (IOException ioe) {
+                    getLogger().warn("unable to load file", ioe);
+                    return;
+                }
+            }
+        };
+        if (useCurrentThread) {
+            runnable.run();
+        } else {
+            Thread thread = new Thread(runnable, "LogFileXMLReceiver-" + getName());
+
+            thread.start();
+
+        }
+    }
+
+    private void process(Reader unbufferedReader) throws IOException {
+        BufferedReader bufferedReader = new BufferedReader(unbufferedReader);
+        char[] content = new char[10000];
+        getLogger().debug("processing starting: " + fileURL);
+        int length = 0;
+        do {
+            System.out.println("in do loop-about to process");
+            while ((length = bufferedReader.read(content)) > -1) {
+                processEvents(decoderInstance.decodeEvents(String.valueOf(content, 0, length)));
+            }
+            if (tailing) {
+                try {
+                    Thread.sleep(5000);
+                } catch (InterruptedException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+        } while (tailing);
+        getLogger().debug("processing complete: " + fileURL);
+
+        shutdown();
+    }
+
+    private void processEvents(Collection c) {
+        if (c == null) {
+            return;
+        }
+
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            LoggingEvent evt = (LoggingEvent) iter.next();
+            if (passesExpression(evt)) {
+                if (evt.getProperty(Constants.HOSTNAME_KEY) != null) {
+                    evt.setProperty(Constants.HOSTNAME_KEY, host);
+                }
+                if (evt.getProperty(Constants.APPLICATION_KEY) != null) {
+                    evt.setProperty(Constants.APPLICATION_KEY, path);
+                }
+                doPost(evt);
+            }
+        }
+    }
+
+    /**
+     * When true, this property uses the current Thread to perform the import, otherwise when false
+     * (the default), a new Thread is created and started to manage the import.
+     * 
+     * @return
+     */
+    public final boolean isUseCurrentThread() {
+        return useCurrentThread;
+    }
+
+    /**
+     * Sets whether the current Thread or a new Thread is created to perform the import, the default
+     * being false (new Thread created).
+     * 
+     * @param useCurrentThread
+     */
+    public final void setUseCurrentThread(boolean useCurrentThread) {
+        this.useCurrentThread = useCurrentThread;
+    }
+
+}
diff --git a/contrib/java/org/haskell/hslogger/XMLDecoder.java b/contrib/java/org/haskell/hslogger/XMLDecoder.java
new file mode 100644
--- /dev/null
+++ b/contrib/java/org/haskell/hslogger/XMLDecoder.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed 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.haskell.hslogger4j;
+
+import java.awt.Component;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.swing.ProgressMonitorInputStream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.xml.*;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+
+/**
+ * Decodes Logging Events in XML formated into elements that are used by
+ * Chainsaw.
+ *
+ * This decoder can process a collection of log4j:event nodes ONLY 
+ * (no XML declaration nor eventSet node)
+ * 
+ * NOTE:  Only a single LoggingEvent is returned from the decode method
+ * even though the DTD supports multiple events nested in an eventSet.
+ *
+ * @author Scott Deboy <sdeboy@apache.org>
+ * @author Paul Smith <psmith@apache.org>
+ *
+ */
+public class XMLDecoder implements Decoder {
+  private static final String BEGINPART =
+    "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><!DOCTYPE log4j:eventSet SYSTEM \"log4j.dtd\"><log4j:eventSet version=\"1.2\" xmlns:log4j=\"http://jakarta.apache.org/log4j/\">";
+  private static final String ENDPART = "</log4j:eventSet>";
+  private static final String RECORD_END = "</log4j:event>";
+  private DocumentBuilderFactory dbf;
+  private DocumentBuilder docBuilder;
+  private Map additionalProperties = new HashMap();
+  private String partialEvent;
+  private Component owner = null;
+
+  public XMLDecoder(Component owner) {
+      this();
+      this.owner = owner;
+  }
+  public XMLDecoder() {
+    dbf = DocumentBuilderFactory.newInstance();
+    dbf.setValidating(false);
+
+    try {
+      docBuilder = dbf.newDocumentBuilder();
+      docBuilder.setErrorHandler(new SAXErrorHandler());
+      docBuilder.setEntityResolver(new Log4jEntityResolver());
+    } catch (ParserConfigurationException pce) {
+      System.err.println("Unable to get document builder");
+    }
+  }
+
+  /**
+   * Sets an additionalProperty map, where each Key/Value pair is
+   * automatically added to each LoggingEvent as it is decoded.
+   *
+   * This is useful, say, to include the source file name of the Logging events
+   * @param additionalProperties
+   */
+  public void setAdditionalProperties(Map additionalProperties) {
+    this.additionalProperties = additionalProperties;
+  }
+
+  /**
+   * Converts the LoggingEvent data in XML string format into an actual
+   * XML Document class instance.
+   * @param data
+   * @return dom document
+   */
+  private Document parse(String data) {
+    if (docBuilder == null || data == null) {
+      return null;
+    }
+    Document document = null;
+
+    try {
+      // we change the system ID to a valid URI so that Crimson won't
+      // complain. Indeed, "log4j.dtd" alone is not a valid URI which
+      // causes Crimson to barf. The Log4jEntityResolver only cares
+      // about the "log4j.dtd" ending.
+      //      buf.setLength(0);
+
+      /**
+       * resetting the length of the StringBuffer is dangerous, particularly
+       * on some JDK 1.4 impls, there's a known Bug that causes a memory leak
+       */
+      StringBuffer buf = new StringBuffer(1024);
+
+      buf.append(BEGINPART);
+      buf.append(data);
+      buf.append(ENDPART);
+
+      InputSource inputSource =
+        new InputSource(new StringReader(buf.toString()));
+      inputSource.setSystemId("dummy://log4j.dtd");
+      document = docBuilder.parse(inputSource);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return document;
+  }
+
+  /**
+   * Decodes a File into a Vector of LoggingEvents
+   * @param url the url of a file containing events to decode
+   * @return Vector of LoggingEvents
+   * @throws IOException
+   */
+  public Vector decode(URL url) throws IOException {
+    LineNumberReader reader = null;
+    if (owner != null) {
+      reader = new LineNumberReader(new InputStreamReader(new ProgressMonitorInputStream(owner, "Loading " + url , url.openStream())));
+    } else {
+      reader = new LineNumberReader(new InputStreamReader(url.openStream()));
+    }
+
+    Vector v = new Vector();
+
+    String line = null;
+	Vector events = null;
+    try {
+        while ((line = reader.readLine()) != null) {
+            StringBuffer buffer = new StringBuffer(line);
+            for (int i = 0;i<1000;i++) {
+                buffer.append(reader.readLine()).append("\n");
+            }
+			events = decodeEvents(buffer.toString());
+            if (events != null)
+            	v.addAll(events);
+        }
+    } finally {
+      partialEvent = null;
+      try {
+        if (reader != null) {
+          reader.close();
+        }
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+    return v;
+  }
+
+  public Vector decodeEvents(String document) {
+    if (document != null) {
+      if (document.trim().equals("")) {
+        return null;
+      }
+	  	String newDoc=null;
+	  	String newPartialEvent=null;
+	  	//separate the string into the last portion ending with </log4j:event> (which will
+	  	//be processed) and the partial event which will be combined and processed in the next section
+	
+		//if the document does not contain a record end, append it to the partial event string
+	  	if (document.lastIndexOf(RECORD_END) == -1) {
+			partialEvent = partialEvent + document;
+	  		return null;
+	  	}
+	
+	  	if (document.lastIndexOf(RECORD_END) + RECORD_END.length() < document.length()) {
+	      	newDoc = document.substring(0, document.lastIndexOf(RECORD_END) + RECORD_END.length());
+			newPartialEvent = document.substring(document.lastIndexOf(RECORD_END) + RECORD_END.length());
+	 	} else {
+	  		newDoc = document;
+	  	}
+		if (partialEvent != null) {
+			newDoc=partialEvent + newDoc;
+		}	      		
+	  	partialEvent=newPartialEvent;
+	    Document doc = parse(newDoc);
+	    if (doc == null) {
+	      return null;
+	    }
+	    return decodeEvents(doc);
+    }
+    return null;
+  }
+
+  /**
+   * Converts the string data into an XML Document, and then soaks out the
+   * relevant bits to form a new LoggingEvent instance which can be used
+   * by any Log4j element locally.
+   * @param data
+   * @return a single LoggingEvent
+   */
+  public LoggingEvent decode(String data) {
+    Document document = parse(data);
+
+    if (document == null) {
+      return null;
+    }
+
+    Vector events = decodeEvents(document);
+
+    if (events.size() > 0) {
+      return (LoggingEvent) events.firstElement();
+    }
+
+    return null;
+  }
+
+  /**
+   * Given a Document, converts the XML into a Vector of LoggingEvents
+   * @param document
+   * @return Vector of LoggingEvents
+   */
+  private Vector decodeEvents(Document document) {
+    Vector events = new Vector();
+
+    Logger logger = null;
+    long timeStamp = 0L;
+    String level = null;
+    String threadName = null;
+    Object message = null;
+    String ndc = null;
+    String[] exception = null;
+    String className = null;
+    String methodName = null;
+    String fileName = null;
+    String lineNumber = null;
+    Hashtable properties = null;
+
+    NodeList nl = document.getElementsByTagName("log4j:eventSet");
+    Node eventSet = nl.item(0);
+
+    NodeList eventList = eventSet.getChildNodes();
+
+    for (int eventIndex = 0; eventIndex < eventList.getLength();
+        eventIndex++) {
+      Node eventNode = eventList.item(eventIndex);
+      //ignore carriage returns in xml 
+	  if(eventNode.getNodeType() != Node.ELEMENT_NODE) {
+	  	continue;
+	  }
+      logger =
+        Logger.getLogger(
+          eventNode.getAttributes().getNamedItem("logger").getNodeValue());
+      timeStamp =
+        Long.parseLong(
+          eventNode.getAttributes().getNamedItem("timestamp").getNodeValue());
+      level =eventNode.getAttributes().getNamedItem("level").getNodeValue();
+      threadName =
+        eventNode.getAttributes().getNamedItem("thread").getNodeValue();
+
+      NodeList list = eventNode.getChildNodes();
+      int listLength = list.getLength();
+
+      for (int y = 0; y < listLength; y++) {
+        String tagName = list.item(y).getNodeName();
+
+        if (tagName.equalsIgnoreCase("log4j:message")) {
+          message = getCData(list.item(y));
+        }
+
+        if (tagName.equalsIgnoreCase("log4j:NDC")) {
+          ndc = getCData(list.item(y));
+        }
+        //still support receiving of MDC and convert to properties
+        if (tagName.equalsIgnoreCase("log4j:MDC")) {
+          properties = new Hashtable();
+          NodeList propertyList = list.item(y).getChildNodes();
+          int propertyLength = propertyList.getLength();
+
+          for (int i = 0; i < propertyLength; i++) {
+            String propertyTag = propertyList.item(i).getNodeName();
+
+            if (propertyTag.equalsIgnoreCase("log4j:data")) {
+              Node property = propertyList.item(i);
+              String name =
+                property.getAttributes().getNamedItem("name").getNodeValue();
+              String value =
+                property.getAttributes().getNamedItem("value").getNodeValue();
+              properties.put(name, value);
+            }
+          }
+        }
+
+        if (tagName.equalsIgnoreCase("log4j:throwable")) {
+          exception = new String[] { getCData(list.item(y)) };
+        }
+
+        if (tagName.equalsIgnoreCase("log4j:properties")) {
+          if (properties == null) {
+              properties = new Hashtable();
+          }
+          NodeList propertyList = list.item(y).getChildNodes();
+          int propertyLength = propertyList.getLength();
+
+          for (int i = 0; i < propertyLength; i++) {
+            String propertyTag = propertyList.item(i).getNodeName();
+
+            if (propertyTag.equalsIgnoreCase("log4j:data")) {
+              Node property = propertyList.item(i);
+              String name =
+                property.getAttributes().getNamedItem("name").getNodeValue();
+              String value =
+                property.getAttributes().getNamedItem("value").getNodeValue();
+              properties.put(name, value);
+            }
+          }
+        }
+
+        /**
+         * We add all the additional properties to the properties
+         * hashtable.  Don't override properties that already exist
+         */
+        if (additionalProperties.size() > 0) {
+          if (properties == null) {
+            properties = new Hashtable(additionalProperties);
+          } else {
+            Iterator i = additionalProperties.entrySet().iterator();
+            while (i.hasNext()) {
+              Map.Entry e = (Map.Entry) i.next();
+              if (!(properties.containsKey(e.getKey()))) {
+              	properties.put(e.getKey(), e.getValue());
+              }
+            }
+          }
+        }
+      }
+      Level levelImpl = HsloggerLevel.toLevel(level);
+      
+      if (exception == null) {
+          exception = new String[]{""};
+      }
+      
+      LoggingEvent loggingEvent = new LoggingEvent();
+      loggingEvent.setLogger(logger);
+      loggingEvent.setTimeStamp(timeStamp);
+      loggingEvent.setLevel(levelImpl);
+      loggingEvent.setThreadName(threadName);
+      loggingEvent.setMessage(message);
+      loggingEvent.setNDC(ndc);
+      loggingEvent.setThrowableInformation(new ThrowableInformation(exception));
+      loggingEvent.setProperties(properties);
+      
+      events.add(loggingEvent);
+     
+      logger = null;
+      timeStamp = 0L;
+      level = null;
+      threadName = null;
+      message = null;
+      ndc = null;
+      exception = null;
+      className = null;
+      methodName = null;
+      fileName = null;
+      lineNumber = null;
+      properties = null;
+    }
+
+    return events;
+  }
+
+  private String getCData(Node n) {
+    StringBuffer buf = new StringBuffer();
+    NodeList nl = n.getChildNodes();
+
+    for (int x = 0; x < nl.getLength(); x++) {
+      Node innerNode = nl.item(x);
+
+      if (
+        (innerNode.getNodeType() == Node.TEXT_NODE)
+          || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
+        buf.append(innerNode.getNodeValue());
+      }
+    }
+
+    return buf.toString();
+  }
+}
diff --git a/hslogger.cabal b/hslogger.cabal
--- a/hslogger.cabal
+++ b/hslogger.cabal
@@ -1,5 +1,5 @@
 Name: hslogger
-Version: 1.0.5
+Version: 1.0.6
 License: LGPL
 Maintainer: John Goerzen <jgoerzen@complete.org>
 Author: John Goerzen
@@ -17,7 +17,13 @@
  or filter messages based on the priority and source.  hslogger also
  has a syslog handler built in.
 Category: Interfaces
-extra-source-files: COPYING
+extra-source-files: COPYING,
+    contrib/java/build.xml,
+    contrib/java/hslogger4j.jar,
+    contrib/java/hslogger4j-plugins.xml,
+    contrib/java/org/haskell/hslogger/HsloggerLevel.java,
+    contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java,
+    contrib/java/org/haskell/hslogger/XMLDecoder.java
 Cabal-Version: >= 1.2
 
 flag small_base
@@ -27,15 +33,17 @@
     Exposed-Modules: 
       System.Log, System.Log.Handler,
         System.Log.Handler.Simple, System.Log.Handler.Syslog,
-        System.Log.Handler.Growl, System.Log.Logger 
+        System.Log.Handler.Growl, System.Log.Handler.Log4jXML,
+        System.Log.Logger
     Extensions: CPP, ExistentialQuantification
     Build-Depends: network, mtl
     if !os(windows)
         Build-Depends: unix
     if flag(small_base)
-        build-depends: base >= 3, containers, directory, process
+        build-depends: base >= 3, containers, directory, process,
+                       time, old-locale
     else
-        build-depends: base < 3
+        build-depends: base < 3, time
     GHC-Options: -O2
     Hs-Source-Dirs: src
 
diff --git a/src/System/Log/Handler/Log4jXML.hs b/src/System/Log/Handler/Log4jXML.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Log/Handler/Log4jXML.hs
@@ -0,0 +1,242 @@
+{- arch-tag: log4j XMLLayout log handler
+Copyright (C) 2007-2008 John Goerzen <jgoerzen@complete.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+-}
+
+{- |
+   Module     : System.Log.Handler.Log4jXML
+   Copyright  : Copyright (C) 2007-2008 John Goerzen
+   License    : GNU LGPL, version 2.1 or above
+
+   Maintainer : bjorn.buckwalter@gmail.com
+   Stability  : experimental
+   Portability: GHC only?
+
+log4j[1] XMLLayout log handlers.
+
+Written by Bjorn Buckwalter, bjorn.buckwalter\@gmail.com
+-}
+
+
+module System.Log.Handler.Log4jXML ( 
+    
+    -- * Introduction
+
+    {- | This module provides handlers for hslogger that are
+    compatible with log4j's XMLLayout. In particular log messages
+    created by the handlers can be published directly to the GUI-based
+    log viewer Chainsaw v2[2].
+
+    The set of log levels in hslogger is richer than the basic set
+    of log4j levels. Two sets of handlers are provided with hslogger4j,
+    one which produces logs with hslogger's levels and one which
+    \"demotes\" them to the basic log4j levels. If full hslogger
+    levels are used some Java installation (see below) is necessary
+    to make Chainsaw aware of them.
+
+    Usage of the handlers in hslogger4j is analoguous to usage of
+    the 'System.Log.Handler.Simple.StreamHandler' and
+    'System.Log.Handler.Simple.FileHandler' in "System.Log.Handler.Simple".
+    The following handlers are provided: -}
+
+    -- ** Handlers with hslogger levels 
+    log4jStreamHandler,
+    log4jFileHandler,
+    
+    -- ** Handlers with log4j levels
+    log4jStreamHandler',
+    log4jFileHandler'
+
+
+    -- * Java install process
+
+    {- | This is only necessary if you want to use the hslogger levels.
+
+    Add @hslogger4j.jar@ from @contrib\/java@ to your classpath.
+    To use you will also need to have the jars @log4j-1.3alpha-7.jar@
+    and @log4j-xml-1.3alpha-7.jar@ that are distributed with Chainsaw
+    on your classpath.
+
+    (On Mac OS X I added all three jars to @~\/Library\/Java\/Extensions@.
+    It seems that it is not sufficient that Chainsaw already includes
+    its jars in the classpath when launching - perhaps the plugin
+    classloader does not inherit Chainsaw's classpath. Adding the
+    jars to @~\/.chainsaw\/plugins@ wouldn't work either.)
+
+    If for whatever reason you have to rebuild the hslogger4j jar
+    just run @ant@[3] in the @contrib\/java@ directory. The new jar
+    will be created in the @contrib\/java\/dist@ directory. The Java
+    source code is copyright The Apache Software Foundation and
+    licensed under the Apache Licence version 2.0. -}
+
+
+    -- * Chainsaw setup
+
+    {- | If you are only using the basic log4j levels just use
+    Chainsaw's regular facilities to browse logs or listen for log
+    messages (e.g. @XMLSocketReceiver@).
+
+    If you want to use the hslogger levels the easiest way to set
+    up Chainsaw is to load the plugins in @hslogger4j-plugins.xml@
+    in @contrib\/java@ when launching Chainsaw. Two receivers will
+    be defined, one that listens for logmessages and one for reading
+    log files.  Edit the properties of those receivers as needed
+    (e.g. @port@, @fileURL@) and restart them. You will also want
+    to modify Chainsaw's formatting preferences to display levels
+    as text instead of icons. -}
+
+
+    -- * Example usage
+
+    {- | In the IO monad:
+
+    > lh2 <- log4jFileHandler "log.xml" DEBUG
+    > updateGlobalLogger rootLoggerName (addHandler lh2)
+
+    > h  <- connectTo "localhost" (PortNumber 4448)
+    > lh <- log4jStreamHandler h NOTICE
+    > updateGlobalLogger rootLoggerName (addHandler lh)
+    -}
+
+    -- * References
+
+    {- |
+    (1) <http://logging.apache.org/log4j/>
+
+    (2) <http://logging.apache.org/chainsaw/>
+
+    (3) <http://ant.apache.org/>
+    -}
+
+    ) where
+
+import Control.Concurrent (ThreadId, myThreadId)  -- myThreadId is GHC only!
+import Control.Concurrent.MVar
+import Data.List (isPrefixOf)
+import System.IO
+import System.Locale (defaultTimeLocale)
+import Data.Time
+import System.Log
+import System.Log.Handler.Simple (GenericHandler (..))
+
+
+-- Handler that logs to a handle rendering message priorities according
+-- to the supplied function.
+log4jHandler :: (Priority -> String) -> Handle -> Priority -> IO (GenericHandler Handle)
+log4jHandler showPrio h pri = do
+    lock <- newMVar ()
+    let mywritefunc hdl (prio, msg) loggername = withMVar lock (\_ -> do
+        time   <- getCurrentTime
+        thread <- myThreadId
+        hPutStrLn hdl (show $ createMessage loggername time prio thread msg)
+        hFlush hdl
+        )
+    return (GenericHandler { priority  = pri,
+                             privData  = h,
+                             writeFunc = mywritefunc,
+                             closeFunc = \x -> return () })
+    where
+        -- Creates an XML element representing a log4j event/message.
+        createMessage :: String -> UTCTime -> Priority -> ThreadId -> String -> XML
+        createMessage logger time prio thread msg = Elem "log4j:event"
+            [ ("logger"   , logger       )
+            , ("timestamp", millis time  )
+            , ("level"    , showPrio prio)
+            , ("thread"   , show thread  )
+            ]
+            (Just $ Elem "log4j:message" [] (Just $ CDATA msg))
+            where
+                -- This is an ugly hack to get a unix epoch with milliseconds.
+                -- The use of "take 3" causes the milliseconds to always be
+                -- rounded downwards, which I suppose may be the expected
+                -- behaviour for time.
+                millis t = formatTime defaultTimeLocale "%s" t
+                    ++ (take 3 $ formatTime defaultTimeLocale "%q" t)
+
+
+-- | Create a stream log handler that uses hslogger priorities.
+log4jStreamHandler :: Handle -> Priority -> IO (GenericHandler Handle)
+log4jStreamHandler = log4jHandler show
+
+{- | Create a stream log handler that uses log4j levels (priorities). The
+   priorities of messages are shoehorned into log4j levels as follows:
+
+@
+    DEBUG                  -> DEBUG
+    INFO, NOTICE           -> INFO
+    WARNING                -> WARN
+    ERROR, CRITICAL, ALERT -> ERROR
+    EMERGENCY              -> FATAL
+@
+
+   This is useful when the log will only be consumed by log4j tools and
+   you don't want to go out of your way transforming the log or configuring
+   the tools. -}
+log4jStreamHandler' :: Handle -> Priority -> IO (GenericHandler Handle)
+log4jStreamHandler' = log4jHandler show' where
+    show' :: Priority -> String
+    show' NOTICE    = "INFO"
+    show' WARNING   = "WARN"
+    show' CRITICAL  = "ERROR"
+    show' ALERT     = "ERROR"
+    show' EMERGENCY = "FATAL"
+    show' p         = show p  -- Identical for DEBUG, INFO, ERROR.
+
+
+-- | Create a file log handler that uses hslogger priorities.
+log4jFileHandler :: FilePath -> Priority -> IO (GenericHandler Handle)
+log4jFileHandler fp pri = do
+                          h <- openFile fp AppendMode
+                          sh <- log4jStreamHandler h pri
+                          return (sh{closeFunc = hClose})
+
+{- | Create a file log handler that uses log4j levels (see
+   'log4jStreamHandler'' for mappings). -}
+log4jFileHandler' :: FilePath -> Priority -> IO (GenericHandler Handle)
+log4jFileHandler' fp pri = do
+                           h <- openFile fp AppendMode
+                           sh <- log4jStreamHandler' h pri
+                           return (sh{closeFunc = hClose})
+
+
+-- A type for building and showing XML elements. Could use a fancy XML
+-- library but am reluctant to introduce dependencies.
+data XML = Elem  String [(String, String)] (Maybe XML)
+         | CDATA String
+
+instance Show XML where
+    show (CDATA s) = "<![CDATA[" ++ escapeCDATA s ++ "]]>" where
+        escapeCDATA = replace "]]>" "]]&lt;"  -- The best we can do, I guess.
+    show (Elem name attrs child) = "<" ++ name ++ showAttrs attrs ++ showChild child where
+        showAttrs []         = ""
+        showAttrs ((k,v):as) = " " ++ k ++ "=\"" ++ escapeAttr v ++ "\""
+                             ++ showAttrs as
+            where escapeAttr = replace "\"" "&quot;"
+                             . replace "<" "&lt;"
+                             . replace "&" "&amp;"
+        showChild Nothing  = "/>"
+        showChild (Just c) = ">" ++ show c ++ "</" ++ name ++ ">"
+
+
+-- Replaces instances of first list by second list in third list.
+-- Definition blatantly stoled from jethr0's comment at
+-- http://bluebones.net/2007/01/replace-in-haskell/. Can be swapped
+-- with definition (or import) from MissingH.
+replace :: Eq a => [a] -> [a] -> [a] -> [a]
+replace _    _  [       ] = []
+replace from to xs@(a:as) = if isPrefixOf from xs
+    then to ++ drop (length from) xs else a : replace from to as
+
diff --git a/src/System/Log/Handler/Simple.hs b/src/System/Log/Handler/Simple.hs
--- a/src/System/Log/Handler/Simple.hs
+++ b/src/System/Log/Handler/Simple.hs
@@ -31,6 +31,7 @@
 -}
 
 module System.Log.Handler.Simple(streamHandler, fileHandler,
+                                      GenericHandler (..),
                                       verboseStreamHandler)
     where
 
@@ -38,6 +39,8 @@
 import System.Log.Handler
 import System.IO
 import Control.Concurrent.MVar
+
+{- | A helper data type. -}
 
 data GenericHandler a = GenericHandler {priority :: Priority,
                                         privData :: a,
diff --git a/src/System/Log/Handler/Syslog.hs b/src/System/Log/Handler/Syslog.hs
--- a/src/System/Log/Handler/Syslog.hs
+++ b/src/System/Log/Handler/Syslog.hs
@@ -96,6 +96,7 @@
               | UUCP                    -- ^ UUCP messages
               | CRON                    -- ^ Cron messages
               | AUTHPRIV                -- ^ Private authentication messages
+              | FTP                     -- ^ FTP messages
               | LOCAL0                  -- ^ LOCAL0 through LOCAL7 are reserved for you to customize as you wish
               | LOCAL1
               | LOCAL2
@@ -119,6 +120,7 @@
                        UUCP -> 8
                        CRON -> 9
                        AUTHPRIV -> 10
+                       FTP -> 11
                        LOCAL0 -> 16
                        LOCAL1 -> 17
                        LOCAL2 -> 18
diff --git a/src/System/Log/Logger.hs b/src/System/Log/Logger.hs
--- a/src/System/Log/Logger.hs
+++ b/src/System/Log/Logger.hs
@@ -180,6 +180,7 @@
 import System.IO.Unsafe
 import Control.Concurrent.MVar
 import Data.List(map, isPrefixOf)
+import Data.Maybe
 import qualified Data.Map as Map
 import qualified Control.Exception
 import Control.Monad.Error
@@ -205,6 +206,7 @@
 
 -- | The name of the root logger, which is always defined and present
 -- on the system.
+rootLoggerName :: String
 rootLoggerName = ""
 
 {- | Placeholders created when a new logger must be created.  This is used
@@ -330,7 +332,7 @@
          Nothing -> do
                     -- Add logger(s).  Then call myself to retrieve it.
                     let newlt = createLoggers (componentsOfName lname) lt
-                    result <- Map.lookup lname newlt
+                    let result = fromJust $ Map.lookup lname newlt
                     return (newlt, result)
     where createLoggers :: [String] -> LogTree -> LogTree
           createLoggers [] lt = lt -- No names to add; return tree unmodified
@@ -346,8 +348,8 @@
           findmodellogger _ [] = error "findmodellogger: root logger does not exist?!"
           findmodellogger lt (x:xs) =
               case Map.lookup x lt of
-                Left (_::String) -> findmodellogger lt xs
-                Right logger -> logger {handlers = []}
+                Nothing -> findmodellogger lt xs
+                Just logger -> logger {handlers = []}
 
 -- | Returns the root logger.
 
