Relocating the zeroconf to a seperate companion module.



git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/companions/zeroconf/trunk@546009 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java b/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java
new file mode 100644
index 0000000..f6a06d2
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/ZeroConfSocketHubAppender.java
@@ -0,0 +1,111 @@
+package org.apache.log4j.net;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+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.
+ * 
+ * @author psmith
+ *
+ */
+public class ZeroConfSocketHubAppender extends SocketHubAppender {
+
+    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;
+    
+    public ZeroConfSocketHubAppender() {
+        setName("SocketHubAppender");
+        try {
+            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) {
+            // 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);
+            jmDNS.registerService(info);
+        } catch (IOException e) {
+            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() );
+    }
+    
+    private void logWithlog4j12Compatibility(Level level, String message) {
+        if(logger!=null && logInfoMethod!=null & logErrorMethod!=null) {
+            try {
+                switch (level.toInt()) {
+                case Level.INFO_INT:
+                    logInfoMethod.invoke(logger, new Object[] { message });
+                    break;
+                case Level.ERROR_INT:
+                    logInfoMethod.invoke(logger, new Object[] { message });
+                    break;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Returns the ZeroConf domain that will be used to register this 'device'.
+     * 
+     * @return String ZeroConf zone
+     */
+    public String getZeroConfZone() {
+        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 ".",
+     * if you're not sure about this value might I suggest that you leave it at the default value
+     * which is specified in {@link #DEFAULT_ZEROCONF_ZONE }.
+     * 
+     * This method does NO(0, zero, pun not intended) checks on this value.
+     * 
+     * @param zeroConfZone
+     */
+    public void setZeroConfZone(String zeroConfZone) {
+//        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 + ")");
+            jmDNS.unregisterService(info);
+        } catch (Exception e) {
+            logWithlog4j12Compatibility(Level.ERROR,"Failed to instantiate JmDNS to broadcast via ZeroConf, will now operate in simple SocketHubAppender mode");
+        }
+    }
+    
+    
+}
diff --git a/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java b/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java
new file mode 100644
index 0000000..0083add
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/Zeroconf4log4j.java
@@ -0,0 +1,58 @@
+package org.apache.log4j.net;
+
+import javax.jmdns.JmDNS;
+
+/**
+ * This singleton holds the single instance of the JmDNS instance that is used to broadcast
+ * Appender related information via ZeroConf.  Once referenced, a single JmDNS instance is created
+ * and held.  To ensure your JVM exits cleanly you should ensure that you call the {@link #shutdown() } method
+ * to broadcast the disappearance of your devices, and cleanup sockets.  (alternatively you can call the close() 
+ * method on the JmDNS instead, totally up to you...)
+ * 
+ * See http://jmdns.sf.net for more information about JmDNS and ZeroConf.
+ * 
+ * @author psmith
+ *
+ */
+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");
+        }
+    }
+
+    /**
+     * Returns the current instance of the JmDNS being used by log4j.
+     * 
+     * @throws IllegalStateException if JmDNS was not correctly initialized.
+     * 
+     * @return
+     */
+    public static JmDNS getInstance() {
+        checkState();
+        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 some Threads exit so your JVM can exit.
+     *
+     */
+    public static void shutdown() {
+        checkState();
+        instance.close();
+    }
+}