Now that log4j 1.2.16 has the new createSocket method, we can trap this, store the details
of the port/address, and use that in the broadcast of what the SocketHubAppender is listening on.

Includes test case.  Changed dependency on zeroconf to require log4j 1.2.16-SNAPSHOT until release.



git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/companions/zeroconf/trunk@634548 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 2bac96a..6143015 100644
--- a/pom.xml
+++ b/pom.xml
@@ -163,6 +163,7 @@
       </plugin>	 
 	  <plugin>
          <artifactId>maven-assembly-plugin</artifactId>
+        
          <configuration>
            <descriptors>
               <descriptor>src/assembly/bin.xml</descriptor>
@@ -171,6 +172,7 @@
         </configuration>
         <executions>
             <execution>
+               <phase>package</phase>
                 <goals>
                     <goal>assembly</goal>
                 </goals>
@@ -181,6 +183,7 @@
         <artifactId>maven-javadoc-plugin</artifactId>
         <executions>
             <execution>
+                <phase>package</phase>
                 <goals>
                     <goal>jar</goal>
                     <goal>javadoc</goal>
@@ -210,7 +213,7 @@
     <dependency>
       <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
-      <version>1.2.9</version>
+      <version>1.2.16-SNAPSHOT</version>
     </dependency>    
     <dependency>
       <groupId>jmdns</groupId>
diff --git a/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java b/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java
index c3d7a75..272663d 100644
--- a/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java
+++ b/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java
@@ -18,60 +18,74 @@
 
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.ServerSocket;
 
 import javax.jmdns.JmDNS;
 import javax.jmdns.ServiceInfo;
 
 import org.apache.log4j.Level;
 
-
 /**
  * A sub-class of SocketHubAppender that broadcasts its configuration via Zeroconf.
  * 
  * This allows Zeroconf aware applications such as Chainsaw to be able to detect them, and automatically configure
  * themselves to be able to connect to them.
  * 
+ * This class relies on log4j 1.2.16 or later.
+ * 
  * @author psmith
  *
  */
 public class ZeroConfSocketHubAppender extends SocketHubAppender {
 
-    public static final String DEFAULT_ZEROCONF_ZONE="_log4j._tcp.local.";
+    public static final String DEFAULT_ZEROCONF_ZONE = "_log4j._tcp.local.";
     private String zeroConfZone = DEFAULT_ZEROCONF_ZONE;
-    
+
     private Object logger;
     private Method logInfoMethod;
     private Method logErrorMethod;
-    
+
+    private int actualPortUsed;
+    private InetAddress actualAddressUsed;
+
     public ZeroConfSocketHubAppender() {
         setName("SocketHubAppender");
         try {
-            Method getLoggerMethod = this.getClass().getMethod("getLogger", new Class[0]);
+            Method getLoggerMethod = this.getClass().getMethod("getLogger",
+                    new Class[0]);
             logger = getLoggerMethod.invoke(this, new Object[0]);
-            logInfoMethod = logger.getClass().getMethod("info", new Class[] {Object.class});
-            logErrorMethod = logger.getClass().getMethod("error", new Class[] {Object.class});
-        }catch(Exception e) {
+            logInfoMethod = logger.getClass().getMethod("info",
+                    new Class[] { Object.class });
+            logErrorMethod = logger.getClass().getMethod("error",
+                    new Class[] { Object.class });
+        } catch (Exception e) {
             // we're not in log4j1.3 land
         }
     }
+
     public void activateOptions() {
         super.activateOptions();
-        
         try {
             JmDNS jmDNS = Zeroconf4log4j.getInstance();
             ServiceInfo info = buildServiceInfo();
-            logWithlog4j12Compatibility(Level.INFO,"Registering this SocketHubAppender as :" + info);
+            logWithlog4j12Compatibility(Level.INFO,
+                    "Registering this SocketHubAppender as :" + info);
             jmDNS.registerService(info);
         } catch (IOException e) {
-            logWithlog4j12Compatibility(Level.ERROR,"Failed to instantiate JmDNS to broadcast via ZeroConf, will now operate in simple SocketHubAppender mode");
+            logWithlog4j12Compatibility(
+                    Level.ERROR,
+                    "Failed to instantiate JmDNS to broadcast via ZeroConf, will now operate in simple SocketHubAppender mode");
         }
     }
+
     private ServiceInfo buildServiceInfo() {
-        return new ServiceInfo(zeroConfZone, getName(), getPort(), "SocketHubAppender on port " + getPort() );
+        return new ServiceInfo(zeroConfZone, getName(), actualPortUsed,
+                "SocketHubAppender on port " + this.actualPortUsed);
     }
-    
+
     private void logWithlog4j12Compatibility(Level level, String message) {
-        if(logger!=null && logInfoMethod!=null & logErrorMethod!=null) {
+        if (logger != null && logInfoMethod != null & logErrorMethod != null) {
             try {
                 switch (level.toInt()) {
                 case Level.INFO_INT:
@@ -96,7 +110,6 @@
         return zeroConfZone;
     }
 
-
     /**
      * Sets the ZeroConf zone to register this device under, BE CAREFUL with this value
      * as ZeroConf has some weird naming conventions, it should start with an "_" and end in a ".",
@@ -108,20 +121,39 @@
      * @param zeroConfZone
      */
     public void setZeroConfZone(String zeroConfZone) {
-//        TODO work out a sane checking mechanism that verifies the value is a correct ZeroConf zone
+        //        TODO work out a sane checking mechanism that verifies the value is a correct ZeroConf zone
         this.zeroConfZone = zeroConfZone;
     }
+
     public synchronized void close() {
         super.close();
         try {
             JmDNS jmDNS = Zeroconf4log4j.getInstance();
             ServiceInfo info = buildServiceInfo();
-            logWithlog4j12Compatibility(Level.INFO,"Deregistering this SocketHubAppender (" + info + ")");
+            logWithlog4j12Compatibility(Level.INFO,
+                    "Deregistering this SocketHubAppender (" + info + ")");
             jmDNS.unregisterService(info);
         } catch (Exception e) {
-            logWithlog4j12Compatibility(Level.ERROR,"Failed to instantiate JmDNS to broadcast via ZeroConf, will now operate in simple SocketHubAppender mode");
+            logWithlog4j12Compatibility(
+                    Level.ERROR,
+                    "Failed to instantiate JmDNS to broadcast via ZeroConf, will now operate in simple SocketHubAppender mode");
         }
     }
-    
-    
+
+    protected ServerSocket createServerSocket(int socketPort)
+            throws IOException {
+        ServerSocket serverSocket = super.createServerSocket(socketPort);
+        this.actualPortUsed = serverSocket.getLocalPort();
+        this.actualAddressUsed = serverSocket.getInetAddress();
+        return serverSocket;
+    }
+
+    public final int getActualPortUsed() {
+        return actualPortUsed;
+    }
+
+    public final InetAddress getActualAddressUsed() {
+        return actualAddressUsed;
+    }
+
 }
diff --git a/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java b/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java
index 77a5508..da21625 100644
--- a/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java
+++ b/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java
@@ -32,43 +32,39 @@
  */
 public class Zeroconf4log4j {
 
-    private static final JmDNS instance;
-
-    static {
-        try {
-            instance = new JmDNS();
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new RuntimeException("Failed to initialize JmDNS");
-        }
-    }
+    private static JmDNS instance;
 
     /**
      * Returns the current instance of the JmDNS being used by log4j.
      * 
-     * @throws IllegalStateException if JmDNS was not correctly initialized.
+     * @throws RuntimeException if JmDNS was not correctly initialized.
      * 
      * @return
      */
-    public static JmDNS getInstance() {
-        checkState();
+    public static synchronized JmDNS getInstance() {
+        if (instance == null) {
+            try {
+                instance = new JmDNS();
+            } catch (Exception e) {
+                throw new RuntimeException(
+                        "Failed to create an instance of JmDNS", e);
+            }
+        }
         return instance;
     }
 
-    private static void checkState() {
-        if (instance == null) {
-            throw new IllegalStateException(
-                    "JmDNS did not initialize correctly");
-        }
-    }
-    
     /**
-     * Ensures JmDNS cleanly broadcasts 'goodbye' and closes any sockets, and (more imporantly)
+     * Ensures JmDNS cleanly broadcasts 'goodbye' and closes any sockets, and (more importantly)
      * ensures some Threads exit so your JVM can exit.
+     * 
+     * This clears an internal {@link JmDNS} variable so that a subsequent call to {@link #getInstance()}
+     * will initialize and create a new one.
      *
      */
     public static void shutdown() {
-        checkState();
-        instance.close();
+        if (instance != null) {
+            instance.close();
+            instance = null;
+        }
     }
 }
diff --git a/src/test/java/org/apache/log4j/net/ZeroConfSocketHubAppenderTest.java b/src/test/java/org/apache/log4j/net/ZeroConfSocketHubAppenderTest.java
index acb761b..7e9930b 100644
--- a/src/test/java/org/apache/log4j/net/ZeroConfSocketHubAppenderTest.java
+++ b/src/test/java/org/apache/log4j/net/ZeroConfSocketHubAppenderTest.java
@@ -2,6 +2,7 @@
 
 import javax.jmdns.JmDNS;
 import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceInfo;
 import javax.jmdns.ServiceListener;
 
 import junit.framework.TestCase;
@@ -10,59 +11,100 @@
  * Some test methods to validate that the ZeroConf stuff works as expected/advertised
  * 
  * @author psmith
- *
  */
 public class ZeroConfSocketHubAppenderTest extends TestCase {
 
+    private final class TestServiceListener implements ServiceListener {
+        final ModifiableBoolean addedFlag = new ModifiableBoolean();
+        final ModifiableBoolean removedFlag = new ModifiableBoolean();
+        private ServiceInfo lastInfo;
+        private ServiceEvent lastEvent;
+
+        public void serviceAdded(ServiceEvent event) {
+            addedFlag.setValue(true);
+            lastEvent = event;
+        }
+
+        public void serviceRemoved(ServiceEvent event) {
+            removedFlag.setValue(true);
+            lastEvent = event;
+        }
+
+        public void serviceResolved(ServiceEvent event) {
+            lastInfo = event.getInfo();
+        }
+    }
+
     private static final int DEFAULT_TIMEOUT_FOR_ZEROCONF_EVENTS_TO_APPEAR = 2000;
 
+    JmDNS jmdns;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        jmdns = Zeroconf4log4j.getInstance();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        Zeroconf4log4j.shutdown();
+
+    }
+
     /**
-     * This does a simple test, as a test harness, to make sure the Appender can be created
-     * and that it can shutdown appropriately.  in older versions of JmDNS a non-daemon thread
-     * could hold the JVM open preventing it from shutting down.
+     * This does a simple test, as a test harness, to make sure the Appender can be created and that
+     * it can shutdown appropriately. in older versions of JmDNS a non-daemon thread could hold the
+     * JVM open preventing it from shutting down.
      * 
-     * @see com.strangeberry.jmdns.tools.Main for a ZeroConf Network browser in Swing allowing you to see the broadcasts
-     * 
+     * @see com.strangeberry.jmdns.tools.Main for a ZeroConf Network browser in Swing allowing you
+     *      to see the broadcasts
      * @throws Exception
      */
     public void testSimpleTest() throws Exception {
-        JmDNS jmdns = Zeroconf4log4j.getInstance();
-        
-        final ModifiableBoolean addedFlag = new ModifiableBoolean();
-        final ModifiableBoolean removedFlag = new ModifiableBoolean();
-        
-        /**
-         * This is just a test to make sure I'm not stupid.
-         */
-        assertTrue(!addedFlag.isSet());
-        assertTrue(!removedFlag.isSet());
-        
-        jmdns.addServiceListener(ZeroConfSocketHubAppender.DEFAULT_ZEROCONF_ZONE, new ServiceListener() {
 
-            public void serviceAdded(ServiceEvent event) {
-                addedFlag.setValue(true);
-               
-            }
-
-            public void serviceRemoved(ServiceEvent event) {
-                removedFlag.setValue(true);
-            }
-
-            public void serviceResolved(ServiceEvent event) {
-                
-            }});
+        TestServiceListener testServiceListener = new TestServiceListener();
+        jmdns.addServiceListener(
+                ZeroConfSocketHubAppender.DEFAULT_ZEROCONF_ZONE,
+                testServiceListener);
         ZeroConfSocketHubAppender appender = new ZeroConfSocketHubAppender();
         appender.setName("SimpleTest");
         appender.activateOptions();
-        
+
         Thread.sleep(DEFAULT_TIMEOUT_FOR_ZEROCONF_EVENTS_TO_APPEAR);
+
+        assertTrue("Should have detected the addition",
+                testServiceListener.addedFlag.isSet());
+
+        appender.close();
+
+        Thread.sleep(DEFAULT_TIMEOUT_FOR_ZEROCONF_EVENTS_TO_APPEAR);
+
+    }
+
+    public void testRandomPortWorksOk() throws Exception {
         
-        assertTrue("Should have detected the addition", addedFlag.isSet());
+        TestServiceListener testServiceListener = new TestServiceListener();
+        jmdns.addServiceListener(
+                ZeroConfSocketHubAppender.DEFAULT_ZEROCONF_ZONE,
+                testServiceListener);
+
+        
+        ZeroConfSocketHubAppender appender = new ZeroConfSocketHubAppender();
+        appender.setPort(0);
+        appender.setName("RandomPortTest");
+        appender.activateOptions();
+        assertTrue("Port should have been automatically chosen", appender
+                .getActualPortUsed() != 0);
+        assertTrue("Should have detected the addition",
+                testServiceListener.addedFlag.isSet());
+
+        ServiceEvent lastEvent = testServiceListener.lastEvent;
+        jmdns.requestServiceInfo(lastEvent.getType(), lastEvent.getName());
+        assertEquals(
+                "The JmDNS port ServiceInfo should have matched what we expect to broadcast from the appender",
+                testServiceListener.lastInfo.getPort(), appender
+                .getActualPortUsed());
         
         appender.close();
-        Zeroconf4log4j.shutdown();
-        
-        Thread.sleep(DEFAULT_TIMEOUT_FOR_ZEROCONF_EVENTS_TO_APPEAR);
-        
+
     }
 }