package org.apache.logging.log4j.core.net;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.util.Integers;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.LoaderUtil;
@Plugin(name = "multicastdns", category = Core.CATEGORY_NAME, elementType = "advertiser", printObject = false)
public class MulticastDnsAdvertiser implements Advertiser {
protected static final Logger LOGGER = StatusLogger.getLogger();
private static final int MAX_LENGTH = 255;
private static final int DEFAULT_PORT = 4555;
private static Object jmDNS = initializeJmDns();
private static Class<?> jmDNSClass;
private static Class<?> serviceInfoClass;
public MulticastDnsAdvertiser() {
}
@Override
public Object advertise(final Map<String, String> properties) {
final Map<String, String> truncatedProperties = new HashMap<>();
for (final Map.Entry<String, String> entry : properties.entrySet()) {
if (entry.getKey().length() <= MAX_LENGTH && entry.getValue().length() <= MAX_LENGTH) {
truncatedProperties.put(entry.getKey(), entry.getValue());
}
}
final String protocol = truncatedProperties.get("protocol");
final String zone = "._log4j._" + (protocol != null ? protocol : "tcp") + ".local.";
final String portString = truncatedProperties.get("port");
final int port = Integers.parseInt(portString, DEFAULT_PORT);
final String name = truncatedProperties.get("name");
if (jmDNS != null) {
boolean isVersion3 = false;
try {
jmDNSClass.getMethod("create");
isVersion3 = true;
} catch (final NoSuchMethodException e) {
}
Object serviceInfo;
if (isVersion3) {
serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties);
} else {
serviceInfo = buildServiceInfoVersion1(zone, port, name, truncatedProperties);
}
try {
final Method method = jmDNSClass.getMethod("registerService", serviceInfoClass);
method.invoke(jmDNS, serviceInfo);
} catch (final IllegalAccessException | InvocationTargetException e) {
LOGGER.warn("Unable to invoke registerService method", e);
} catch (final NoSuchMethodException e) {
LOGGER.warn("No registerService method", e);
}
return serviceInfo;
}
LOGGER.warn("JMDNS not available - will not advertise ZeroConf support");
return null;
}
@Override
public void unadvertise(final Object serviceInfo) {
if (jmDNS != null) {
try {
final Method method = jmDNSClass.getMethod("unregisterService", serviceInfoClass);
method.invoke(jmDNS, serviceInfo);
} catch (final IllegalAccessException | InvocationTargetException e) {
LOGGER.warn("Unable to invoke unregisterService method", e);
} catch (final NoSuchMethodException e) {
LOGGER.warn("No unregisterService method", e);
}
}
}
private static Object createJmDnsVersion1() {
try {
return jmDNSClass.getConstructor().newInstance();
} catch (final InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
LOGGER.warn("Unable to instantiate JMDNS", e);
}
return null;
}
private static Object createJmDnsVersion3() {
try {
final Method jmDNSCreateMethod = jmDNSClass.getMethod("create");
return jmDNSCreateMethod.invoke(null, (Object[]) null);
} catch (final IllegalAccessException | InvocationTargetException e) {
LOGGER.warn("Unable to invoke create method", e);
} catch (final NoSuchMethodException e) {
LOGGER.warn("Unable to get create method", e);
}
return null;
}
private static Object buildServiceInfoVersion1(final String zone, final int port, final String name,
final Map<String, String> properties) {
@SuppressWarnings("UseOfObsoleteCollectionType")
final Hashtable<String, String> hashtableProperties = new Hashtable<>(properties);
try {
return serviceInfoClass.getConstructor(String.class, String.class, int.class, int.class, int.class,
Hashtable.class).newInstance(zone, name, port, 0, 0, hashtableProperties);
} catch (final IllegalAccessException | InstantiationException | InvocationTargetException e) {
LOGGER.warn("Unable to construct ServiceInfo instance", e);
} catch (final NoSuchMethodException e) {
LOGGER.warn("Unable to get ServiceInfo constructor", e);
}
return null;
}
private static Object buildServiceInfoVersion3(final String zone, final int port, final String name,
final Map<String, String> properties) {
try {
return serviceInfoClass
.getMethod("create", String.class, String.class, int.class, int.class, int.class, Map.class)
.invoke(null, zone, name, port, 0, 0, properties);
} catch (final IllegalAccessException | InvocationTargetException e) {
LOGGER.warn("Unable to invoke create method", e);
} catch (final NoSuchMethodException e) {
LOGGER.warn("Unable to find create method", e);
}
return null;
}
private static Object initializeJmDns() {
try {
jmDNSClass = LoaderUtil.loadClass("javax.jmdns.JmDNS");
serviceInfoClass = LoaderUtil.loadClass("javax.jmdns.ServiceInfo");
boolean isVersion3 = false;
try {
jmDNSClass.getMethod("create");
isVersion3 = true;
} catch (final NoSuchMethodException e) {
}
if (isVersion3) {
return createJmDnsVersion3();
}
return createJmDnsVersion1();
} catch (final ClassNotFoundException | ExceptionInInitializerError e) {
LOGGER.warn("JmDNS or serviceInfo class not found", e);
}
return null;
}
}