blob: fc7daf2c5bd386fd270d97221148d38f03ce1035 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.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.plugins.Plugin;
import org.apache.logging.log4j.core.util.Integers;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.LoaderUtil;
/**
* Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library.
*
* The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255 bytes
* will be removed prior to advertisement.
*
*/
@Plugin(name = "multicastdns", category = Core.CATEGORY_NAME, elementType = "advertiser", printObject = false)
public class MulticastDnsAdvertiser implements Advertiser {
/**
* Status logger.
*/
protected static final Logger LOGGER = StatusLogger.getLogger();
private static final int MAX_LENGTH = 255;
private static final int DEFAULT_PORT = 4555;
private static final Object jmDNS = initializeJmDns();
private static Class<?> jmDNSClass;
private static Class<?> serviceInfoClass;
public MulticastDnsAdvertiser() {
// no arg constructor for reflection
}
/**
* Advertise the provided entity.
*
* Properties map provided in advertise method must include a "name" entry but may also provide "protocol" (tcp/udp)
* as well as a "port" entry
*
* The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255
* bytes will be removed prior to advertisement.
*
* @param properties the properties representing the entity to advertise
* @return the object which can be used to unadvertise, or null if advertisement was unsuccessful
*/
@Override
public Object advertise(final Map<String, String> properties) {
// default to tcp if "protocol" was not set
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.";
// default to 4555 if "port" was not set
final String portString = truncatedProperties.get("port");
final int port = Integers.parseInt(portString, DEFAULT_PORT);
final String name = truncatedProperties.get("name");
// if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
if (jmDNS != null) {
boolean isVersion3 = false;
try {
// create method is in version 3, not version 1
jmDNSClass.getMethod("create");
isVersion3 = true;
} catch (final NoSuchMethodException e) {
// no-op
}
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;
}
/**
* Unadvertise the previously advertised entity.
*
* @param serviceInfo
*/
@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) {
// version 1 uses a hashtable
@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
// zone/type display name port weight priority properties
.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");
// if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
boolean isVersion3 = false;
try {
// create method is in version 3, not version 1
jmDNSClass.getMethod("create");
isVersion3 = true;
} catch (final NoSuchMethodException e) {
// no-op
}
if (isVersion3) {
return createJmDnsVersion3();
}
return createJmDnsVersion1();
} catch (final ClassNotFoundException | ExceptionInInitializerError e) {
LOGGER.warn("JmDNS or serviceInfo class not found", e);
}
return null;
}
}