001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.net; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.util.HashMap; 022import java.util.Hashtable; 023import java.util.Map; 024 025import org.apache.logging.log4j.Logger; 026import org.apache.logging.log4j.core.Core; 027import org.apache.logging.log4j.core.config.plugins.Plugin; 028import org.apache.logging.log4j.core.util.Integers; 029import org.apache.logging.log4j.status.StatusLogger; 030import org.apache.logging.log4j.util.LoaderUtil; 031 032/** 033 * Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library. 034 * 035 * The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255 bytes 036 * will be removed prior to advertisement. 037 * 038 */ 039@Plugin(name = "multicastdns", category = Core.CATEGORY_NAME, elementType = "advertiser", printObject = false) 040public class MulticastDnsAdvertiser implements Advertiser { 041 /** 042 * Status logger. 043 */ 044 protected static final Logger LOGGER = StatusLogger.getLogger(); 045 046 private static final int MAX_LENGTH = 255; 047 private static final int DEFAULT_PORT = 4555; 048 049 private static Object jmDNS = initializeJmDns(); 050 private static Class<?> jmDNSClass; 051 private static Class<?> serviceInfoClass; 052 053 public MulticastDnsAdvertiser() { 054 // no arg constructor for reflection 055 } 056 057 /** 058 * Advertise the provided entity. 059 * 060 * Properties map provided in advertise method must include a "name" entry but may also provide "protocol" (tcp/udp) 061 * as well as a "port" entry 062 * 063 * The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255 064 * bytes will be removed prior to advertisement. 065 * 066 * @param properties the properties representing the entity to advertise 067 * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful 068 */ 069 @Override 070 public Object advertise(final Map<String, String> properties) { 071 // default to tcp if "protocol" was not set 072 final Map<String, String> truncatedProperties = new HashMap<>(); 073 for (final Map.Entry<String, String> entry : properties.entrySet()) { 074 if (entry.getKey().length() <= MAX_LENGTH && entry.getValue().length() <= MAX_LENGTH) { 075 truncatedProperties.put(entry.getKey(), entry.getValue()); 076 } 077 } 078 final String protocol = truncatedProperties.get("protocol"); 079 final String zone = "._log4j._" + (protocol != null ? protocol : "tcp") + ".local."; 080 // default to 4555 if "port" was not set 081 final String portString = truncatedProperties.get("port"); 082 final int port = Integers.parseInt(portString, DEFAULT_PORT); 083 084 final String name = truncatedProperties.get("name"); 085 086 // if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API 087 if (jmDNS != null) { 088 boolean isVersion3 = false; 089 try { 090 // create method is in version 3, not version 1 091 jmDNSClass.getMethod("create"); 092 isVersion3 = true; 093 } catch (final NoSuchMethodException e) { 094 // no-op 095 } 096 Object serviceInfo; 097 if (isVersion3) { 098 serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties); 099 } else { 100 serviceInfo = buildServiceInfoVersion1(zone, port, name, truncatedProperties); 101 } 102 103 try { 104 final Method method = jmDNSClass.getMethod("registerService", serviceInfoClass); 105 method.invoke(jmDNS, serviceInfo); 106 } catch (final IllegalAccessException | InvocationTargetException e) { 107 LOGGER.warn("Unable to invoke registerService method", e); 108 } catch (final NoSuchMethodException e) { 109 LOGGER.warn("No registerService method", e); 110 } 111 return serviceInfo; 112 } 113 LOGGER.warn("JMDNS not available - will not advertise ZeroConf support"); 114 return null; 115 } 116 117 /** 118 * Unadvertise the previously advertised entity. 119 * 120 * @param serviceInfo 121 */ 122 @Override 123 public void unadvertise(final Object serviceInfo) { 124 if (jmDNS != null) { 125 try { 126 final Method method = jmDNSClass.getMethod("unregisterService", serviceInfoClass); 127 method.invoke(jmDNS, serviceInfo); 128 } catch (final IllegalAccessException | InvocationTargetException e) { 129 LOGGER.warn("Unable to invoke unregisterService method", e); 130 } catch (final NoSuchMethodException e) { 131 LOGGER.warn("No unregisterService method", e); 132 } 133 } 134 } 135 136 private static Object createJmDnsVersion1() { 137 try { 138 return jmDNSClass.getConstructor().newInstance(); 139 } catch (final InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 140 LOGGER.warn("Unable to instantiate JMDNS", e); 141 } 142 return null; 143 } 144 145 private static Object createJmDnsVersion3() { 146 try { 147 final Method jmDNSCreateMethod = jmDNSClass.getMethod("create"); 148 return jmDNSCreateMethod.invoke(null, (Object[]) null); 149 } catch (final IllegalAccessException | InvocationTargetException e) { 150 LOGGER.warn("Unable to invoke create method", e); 151 } catch (final NoSuchMethodException e) { 152 LOGGER.warn("Unable to get create method", e); 153 } 154 return null; 155 } 156 157 private static Object buildServiceInfoVersion1(final String zone, final int port, final String name, 158 final Map<String, String> properties) { 159 // version 1 uses a hashtable 160 @SuppressWarnings("UseOfObsoleteCollectionType") 161 final Hashtable<String, String> hashtableProperties = new Hashtable<>(properties); 162 try { 163 return serviceInfoClass.getConstructor(String.class, String.class, int.class, int.class, int.class, 164 Hashtable.class).newInstance(zone, name, port, 0, 0, hashtableProperties); 165 } catch (final IllegalAccessException | InstantiationException | InvocationTargetException e) { 166 LOGGER.warn("Unable to construct ServiceInfo instance", e); 167 } catch (final NoSuchMethodException e) { 168 LOGGER.warn("Unable to get ServiceInfo constructor", e); 169 } 170 return null; 171 } 172 173 private static Object buildServiceInfoVersion3(final String zone, final int port, final String name, 174 final Map<String, String> properties) { 175 try { 176 return serviceInfoClass 177 // zone/type display name port weight priority properties 178 .getMethod("create", String.class, String.class, int.class, int.class, int.class, Map.class) 179 .invoke(null, zone, name, port, 0, 0, properties); 180 } catch (final IllegalAccessException | InvocationTargetException e) { 181 LOGGER.warn("Unable to invoke create method", e); 182 } catch (final NoSuchMethodException e) { 183 LOGGER.warn("Unable to find create method", e); 184 } 185 return null; 186 } 187 188 private static Object initializeJmDns() { 189 try { 190 jmDNSClass = LoaderUtil.loadClass("javax.jmdns.JmDNS"); 191 serviceInfoClass = LoaderUtil.loadClass("javax.jmdns.ServiceInfo"); 192 // if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API 193 boolean isVersion3 = false; 194 try { 195 // create method is in version 3, not version 1 196 jmDNSClass.getMethod("create"); 197 isVersion3 = true; 198 } catch (final NoSuchMethodException e) { 199 // no-op 200 } 201 202 if (isVersion3) { 203 return createJmDnsVersion3(); 204 } 205 return createJmDnsVersion1(); 206 } catch (final ClassNotFoundException | ExceptionInInitializerError e) { 207 LOGGER.warn("JmDNS or serviceInfo class not found", e); 208 } 209 return null; 210 } 211}