Added ZeroConf support to most of the network-based appenders and receivers.

To enable ZeroConf advertising of an appender or receiver, a user can now:
 - add jmdns jar to their classpath 
 - set the 'advertiseViaMulticastDNS' param to 'true'

Also added support for discovery of appenders in Chainsaw (Chainsaw will automatically configure a matching receiver when connected).
Supported appenders:
    SocketAppender
    SocketHubAppender (ZeroConfSocketHubAppender is still functional)
    UDPAppender
    MulticastAppender
Supported receivers:
    SocketReceiver
    SocketHubReceiver (also receives events from ZeroConfSocketHubAppender)
    XMLSocketReceiver (can receive events sent over TCP by other logging frameworks)
    UDPReceiver
    MulticastReceiver

Implementation details:
 - removed Zeroconf4log4j class, jmdns access is now through the ZeroConfSupport class (class provides support of JmDNS and ServiceInfo creation via reflection, and supports both jmdns 1.0 and jmdns 3.1 apis)
 - ZeroConfSupport class is now used by ZeroConfSocketHubAppender, Chainsaw and all appenders & receivers that can advertise their configuration via ZeroConf
 - added new 'advertiseViaMulticastDNS' param to the appenders and receivers that support ZeroConf
 - updated the ZeroConf site documentation
 - updated release notes
 - updated the ZeroConfPlugin html file
 - tested appenders with 1.0 and 3.1 jmdns jars
 - updated log4j references in poms to log4j 1.2.16-snapshot where necessary 

For those wanting to add ZeroConf support to third-party appenders and have Chainsaw discover the appenders, the service info naming convention is described here: https://issues.apache.org/bugzilla/show_bug.cgi?id=48907


git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/companions/receivers/trunk@924176 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index c6073f2..ae0aa5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,7 +217,7 @@
     <dependency>
       <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
-      <version>1.2.15</version>
+      <version>1.2.16-SNAPSHOT</version>
       <exclusions>
         <exclusion>
           <groupId>javax.jms</groupId>
diff --git a/src/main/java/org/apache/log4j/net/MulticastAppender.java b/src/main/java/org/apache/log4j/net/MulticastAppender.java
index b46f546..de002c5 100644
--- a/src/main/java/org/apache/log4j/net/MulticastAppender.java
+++ b/src/main/java/org/apache/log4j/net/MulticastAppender.java
@@ -22,11 +22,15 @@
 import java.net.InetAddress;
 import java.net.MulticastSocket;
 import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
 
 import org.apache.log4j.AppenderSkeleton;
 import org.apache.log4j.helpers.Constants;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.xml.XMLLayout;
 
 
 /**
@@ -54,7 +58,13 @@
      The default port number for the multicast packets. (9991).
   */
   static final int DEFAULT_PORT = 9991;
-  
+
+  /**
+   * The MulticastDNS zone advertised by a MulticastAppender
+   * the MulticastAppender also adds a 'multicastAddress' property with the multicast address value as a string
+   */
+  public static final String ZONE = "_log4j_xml_mcast_appender.local.";
+
   /**
      We remember host name as String in addition to the resolved
      InetAddress so that it can be returned via getOption().
@@ -69,7 +79,9 @@
   private String encoding;
 
   private boolean locationInfo = false;
-  
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
   public MulticastAppender() {
      super(false);
   }
@@ -100,11 +112,21 @@
     if(remoteHost != null) {
       address = getAddressByName(remoteHost);
     } else {
-      String err = "The RemoteHost property is required for SocketAppender named "+ name;
+      String err = "The RemoteHost property is required for MulticastAppender named "+ name;
       LogLog.error(err);
       throw new IllegalStateException(err);
     }
-    
+
+    if (layout == null) {
+        layout = new XMLLayout();
+    }
+      
+    if (advertiseViaMulticastDNS) {
+        Map properties = new HashMap();
+        properties.put("multicastAddress", remoteHost);
+        zeroConf = new ZeroConfSupport(ZONE, port, getName(), properties);
+        zeroConf.advertise();
+    }
     connect();
     super.activateOptions();
   }
@@ -120,6 +142,9 @@
     }
 
     this.closed = true;
+    if (advertiseViaMulticastDNS) {
+        zeroConf.unadvertise();
+    }
     cleanUp();
   }
 
@@ -310,4 +335,11 @@
       return true;
   }
 
+  public boolean isAdvertiseViaMulticastDNS() {
+      return advertiseViaMulticastDNS;
+  }
+
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+      this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
 }
diff --git a/src/main/java/org/apache/log4j/net/MulticastReceiver.java b/src/main/java/org/apache/log4j/net/MulticastReceiver.java
index 4788b03..ebba2c2 100644
--- a/src/main/java/org/apache/log4j/net/MulticastReceiver.java
+++ b/src/main/java/org/apache/log4j/net/MulticastReceiver.java
@@ -58,6 +58,13 @@
   private MulticastHandlerThread handlerThread;
   private MulticastReceiverThread receiverThread;
   private boolean paused;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+  /**
+   * The MulticastDNS zone advertised by a MulticastReceiver
+   */
+  public static final String ZONE = "_log4j_xml_mcast_receiver.local.";
 
   public String getDecoder() {
     return decoder;
@@ -96,6 +103,9 @@
 
   public synchronized void shutdown() {
     isActive = false;
+    if (advertiseViaMulticastDNS) {
+        zeroConf.unadvertise();
+    }
     handlerThread.interrupt();
     receiverThread.interrupt();
     socket.close();
@@ -113,12 +123,6 @@
     paused = b;
   }
 
-  /**
-    Returns true if this receiver is active. */
-  public synchronized boolean isActive() {
-    return isActive;
-  }
-
   public void activateOptions() {
     InetAddress addr = null;
 
@@ -151,12 +155,25 @@
       receiverThread.start();
       handlerThread = new MulticastHandlerThread();
       handlerThread.start();
+      if (advertiseViaMulticastDNS) {
+        zeroConf = new ZeroConfSupport(ZONE, port, getName());
+        zeroConf.advertise();
+      }
+
     } catch (IOException ioe) {
       ioe.printStackTrace();
     }
   }
 
-  class MulticastHandlerThread extends Thread {
+    public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+        this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+    }
+
+    public boolean isAdvertiseViaMulticastDNS() {
+        return advertiseViaMulticastDNS;
+    }
+
+    class MulticastHandlerThread extends Thread {
     private List list = new ArrayList();
 
     public MulticastHandlerThread() {
diff --git a/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java b/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
index 32ff8df..4dec14c 100644
--- a/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
+++ b/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
@@ -41,6 +41,7 @@
                 new PropertyDescriptor("port", MulticastReceiver.class),
                 new PropertyDescriptor("threshold", MulticastReceiver.class),
                 new PropertyDescriptor("decoder", MulticastReceiver.class),
+                new PropertyDescriptor("advertiseViaMulticastDNS", MulticastReceiver.class),
             };
         } catch (Exception e) {
         }
diff --git a/src/main/java/org/apache/log4j/net/SocketHubReceiver.java b/src/main/java/org/apache/log4j/net/SocketHubReceiver.java
index bef1095..85058e8 100644
--- a/src/main/java/org/apache/log4j/net/SocketHubReceiver.java
+++ b/src/main/java/org/apache/log4j/net/SocketHubReceiver.java
@@ -63,6 +63,11 @@
      */
   protected int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
 
+  /**
+   * The MulticastDNS zone advertised by a SocketHubReceiver
+   */
+  public static final String ZONE = "_log4j_obj_tcpconnect_receiver.local.";
+
     /**
      * Active.
      */
@@ -83,6 +88,9 @@
      */
   private List listenerList = Collections.synchronizedList(new ArrayList());
 
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
     /**
      * Create new instance.
      */
@@ -223,14 +231,6 @@
   }
 
   /**
-    Returns true if this receiver is active.
-   @return true if receiver is active
-   */
-  public synchronized boolean isActive() {
-    return active;
-  }
-
-  /**
     Sets the flag to indicate if receiver is active or not.
    @param b new value
    */
@@ -243,6 +243,11 @@
   public void activateOptions() {
     if (!isActive()) {
       setActive(true);
+      if (advertiseViaMulticastDNS) {
+        zeroConf = new ZeroConfSupport(ZONE, port, getName());
+        zeroConf.advertise();
+      }
+
       fireConnector(false);
     }
   }
@@ -269,6 +274,9 @@
       connector.interrupted = true;
       connector = null;  // allow gc
     }
+    if (advertiseViaMulticastDNS) {
+        zeroConf.unadvertise();
+    }
   }
 
   /**
@@ -320,6 +328,14 @@
     new Thread(socketNode).start();
   }
 
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+      this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+      return advertiseViaMulticastDNS;
+  }
+
   /**
    The Connector will reconnect when the server becomes available
    again.  It does this by attempting to open a new connection every
diff --git a/src/main/java/org/apache/log4j/net/SocketReceiver.java b/src/main/java/org/apache/log4j/net/SocketReceiver.java
index 8e6cfad..9d4aac9 100644
--- a/src/main/java/org/apache/log4j/net/SocketReceiver.java
+++ b/src/main/java/org/apache/log4j/net/SocketReceiver.java
@@ -75,6 +75,12 @@
      * Socket list.
      */
   private Vector socketList = new Vector();
+
+  /**
+   * The MulticastDNS zone advertised by a SocketReceiver
+   */
+  public static final String ZONE = "_log4j_obj_tcpaccept_receiver.local.";
+
     /**
      * Listener.
      */
@@ -83,6 +89,8 @@
      * Listeners.
      */
   private List listenerList = Collections.synchronizedList(new ArrayList());
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
 
     /**
      * Create new instance.
@@ -148,6 +156,11 @@
       rThread = new Thread(this);
       rThread.setDaemon(true);
       rThread.start();
+      if (advertiseViaMulticastDNS) {
+        zeroConf = new ZeroConfSupport(ZONE, port, getName());
+        zeroConf.advertise();
+      }
+
       active = true;
     }
   }
@@ -166,6 +179,9 @@
       rThread.interrupt();
       rThread = null;
     }
+    if (advertiseViaMulticastDNS) {
+        zeroConf.unadvertise();
+    }
 
     doShutdown();
   }
@@ -221,6 +237,22 @@
   }
 
   /**
+    Sets the flag to indicate if receiver is active or not.
+   @param b new value
+   */
+  protected synchronized void setActive(final boolean b) {
+    active = b;
+  }
+
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+      this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+      return advertiseViaMulticastDNS;
+  }
+
+  /**
     Loop, accepting new socket connections. */
   public void run() {
     /**
diff --git a/src/main/java/org/apache/log4j/net/UDPAppender.java b/src/main/java/org/apache/log4j/net/UDPAppender.java
index a1ce9f7..55bc6f1 100644
--- a/src/main/java/org/apache/log4j/net/UDPAppender.java
+++ b/src/main/java/org/apache/log4j/net/UDPAppender.java
@@ -20,8 +20,8 @@
 import org.apache.log4j.AppenderSkeleton;
 import org.apache.log4j.helpers.Constants;
 import org.apache.log4j.spi.LoggingEvent;
-import org.apache.log4j.xml.XMLLayout;
 import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.xml.XMLLayout;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
@@ -68,11 +68,18 @@
   int port = DEFAULT_PORT;
   DatagramSocket outSocket;
 
+  /**
+   * The MulticastDNS zone advertised by a UDPAppender
+   */
+  public static final String ZONE = "_log4j_xml_udp_appender.local.";
+
   // if there is something irrecoverably wrong with the settings, there is no
   // point in sending out packeets.
   boolean inError = false;
-  
-  public UDPAppender() {
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+    public UDPAppender() {
       super(false);
   }
 
@@ -129,6 +136,16 @@
       LogLog.error(err);
       throw new IllegalStateException(err);
     }
+
+    if (layout == null) {
+        layout = new XMLLayout();
+    }
+
+    if (advertiseViaMulticastDNS) {
+      zeroConf = new ZeroConfSupport(ZONE, port, getName());
+      zeroConf.advertise();
+    }
+
     super.activateOptions();
   }
 
@@ -142,6 +159,10 @@
       return;
     }
 
+    if (advertiseViaMulticastDNS) {
+      zeroConf.unadvertise();
+    }
+      
     this.closed = true;
     cleanUp();
   }
@@ -199,7 +220,6 @@
       }
 
       try {
-        // TODO UDPAppender throws NullPointerException if the layout is not set
         StringBuffer buf = new StringBuffer(layout.format(event));
 
         byte[] payload;
@@ -300,4 +320,11 @@
     return port;
   }
 
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
 }
diff --git a/src/main/java/org/apache/log4j/net/UDPReceiver.java b/src/main/java/org/apache/log4j/net/UDPReceiver.java
index 98567bd..63eb275 100644
--- a/src/main/java/org/apache/log4j/net/UDPReceiver.java
+++ b/src/main/java/org/apache/log4j/net/UDPReceiver.java
@@ -51,8 +51,16 @@
   private int port;
   private DatagramSocket socket;
   UDPHandlerThread handlerThread;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
 
-  public int getPort() {
+  /**
+   * The MulticastDNS zone advertised by a UDPReceiver
+   */
+  public static final String ZONE = "_log4j_xml_udp_receiver.local.";
+
+
+    public int getPort() {
     return port;
   }
 
@@ -91,6 +99,14 @@
     paused = b;
   }
 
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
+
   public synchronized void shutdown() {
     if(closed == true) {
       return;
@@ -102,6 +118,10 @@
     	socket.close();
     }
 
+    if (advertiseViaMulticastDNS) {
+      zeroConf.unadvertise();
+    }
+      
     try {
       if(handlerThread != null) {
       	handlerThread.close();
@@ -142,6 +162,11 @@
       receiverThread.start();
       handlerThread = new UDPHandlerThread();
       handlerThread.start();
+      if (advertiseViaMulticastDNS) {
+        zeroConf = new ZeroConfSupport(ZONE, port, getName());
+        zeroConf.advertise();
+      }
+
     } catch (IOException ioe) {
       ioe.printStackTrace();
     }
@@ -160,7 +185,7 @@
         list.notify();
       }
     }
-  
+
     /**
      * Allow the UDPHandlerThread to wakeup and exit gracefully.
      */
@@ -177,7 +202,7 @@
         synchronized (list) {
           try {
             while (!UDPReceiver.this.closed && list.size() == 0) {
-              list.wait();
+              list.wait(300);
             }
 
             if (list.size() > 0) {
diff --git a/src/main/java/org/apache/log4j/net/XMLSocketReceiver.java b/src/main/java/org/apache/log4j/net/XMLSocketReceiver.java
index 55dbcca..fe18758 100644
--- a/src/main/java/org/apache/log4j/net/XMLSocketReceiver.java
+++ b/src/main/java/org/apache/log4j/net/XMLSocketReceiver.java
@@ -60,6 +60,18 @@
   private Thread rThread;
   public static final int DEFAULT_PORT = 4448;
   protected int port = DEFAULT_PORT;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+  /**
+   * The MulticastDNS zone advertised by an XMLSocketReceiver
+   */
+  public static final String ZONE = "_log4j_xml_tcpaccept_receiver.local.";
+
+  /*
+   * Log4j doesn't provide an XMLSocketAppender, but the MulticastDNS zone that should be advertised by one is:
+   * _log4j_xml_tcpconnect_appender.local.
+   */
 
   public XMLSocketReceiver() {
   }
@@ -129,11 +141,13 @@
   	result = result * 37 + port;
   	return (result * 37 + (getName() != null? getName().hashCode():0));
   }
-  	
+
   /**
-    Returns true if this receiver is active. */
-  public synchronized boolean isActive() {
-    return active;
+    Sets the flag to indicate if receiver is active or not.
+   @param b new value
+   */
+  protected synchronized void setActive(final boolean b) {
+    active = b;
   }
 
   /**
@@ -143,10 +157,24 @@
       rThread = new Thread(this);
       rThread.setDaemon(true);
       rThread.start();
+
+      if (advertiseViaMulticastDNS) {
+        zeroConf = new ZeroConfSupport(ZONE, port, getName());
+        zeroConf.advertise();
+      }
+
       active = true;
     }
   }
 
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
+
   /**
     Called when the receiver should be stopped. Closes the
     server socket and all of the open sockets. */
@@ -175,6 +203,10 @@
 
       // close all of the accepted sockets
       closeAllAcceptedSockets();
+
+      if (advertiseViaMulticastDNS) {
+          zeroConf.unadvertise();
+      }
     }
 
     /**