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.config.plugins.Plugin; 027import org.apache.logging.log4j.core.util.Integers; 028import org.apache.logging.log4j.core.util.Loader; 029import org.apache.logging.log4j.status.StatusLogger; 030 031/** 032 * Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library. 033 * 034 * The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255 bytes 035 * will be removed prior to advertisement. 036 * 037 */ 038@Plugin(name = "multicastdns", category = "Core", elementType = "advertiser", printObject = false) 039public class MulticastDnsAdvertiser implements Advertiser { 040 /** 041 * 042 */ 043 private static final int MAX_LENGTH = 255; 044 /** 045 * Status logger. 046 */ 047 protected static final Logger LOGGER = StatusLogger.getLogger(); 048 private static final int DEFAULT_PORT = 4555; 049 050 private static Object jmDNS = initializeJmDns(); 051 private static Class<?> jmDNSClass; 052 private static Class<?> serviceInfoClass; 053 054 public MulticastDnsAdvertiser() { 055 // no arg constructor for reflection 056 } 057 058 /** 059 * Advertise the provided entity. 060 * 061 * Properties map provided in advertise method must include a "name" entry but may also provide "protocol" (tcp/udp) 062 * as well as a "port" entry 063 * 064 * The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255 065 * bytes will be removed prior to advertisement. 066 * 067 * @param properties the properties representing the entity to advertise 068 * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful 069 */ 070 @Override 071 public Object advertise(final Map<String, String> properties) { 072 // default to tcp if "protocol" was not set 073 final Map<String, String> truncatedProperties = new HashMap<>(); 074 for (final Map.Entry<String, String> entry : properties.entrySet()) { 075 if (entry.getKey().length() <= MAX_LENGTH && entry.getValue().length() <= MAX_LENGTH) { 076 truncatedProperties.put(entry.getKey(), entry.getValue()); 077 } 078 } 079 final String protocol = truncatedProperties.get("protocol"); 080 final String zone = "._log4j._" + (protocol != null ? protocol : "tcp") + ".local."; 081 // default to 4555 if "port" was not set 082 final String portString = truncatedProperties.get("port"); 083 final int port = Integers.parseInt(portString, DEFAULT_PORT); 084 085 final String name = truncatedProperties.get("name"); 086 087 // if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API 088 if (jmDNS != null) { 089 boolean isVersion3 = false; 090 try { 091 // create method is in version 3, not version 1 092 jmDNSClass.getMethod("create"); 093 isVersion3 = true; 094 } catch (final NoSuchMethodException e) { 095 // no-op 096 } 097 Object serviceInfo; 098 if (isVersion3) { 099 serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties); 100 } else { 101 serviceInfo = buildServiceInfoVersion1(zone, port, name, truncatedProperties); 102 } 103 104 try { 105 final Method method = jmDNSClass.getMethod("registerService", serviceInfoClass); 106 method.invoke(jmDNS, serviceInfo); 107 } catch (final IllegalAccessException e) { 108 LOGGER.warn("Unable to invoke registerService method", e); 109 } catch (final NoSuchMethodException e) { 110 LOGGER.warn("No registerService method", e); 111 } catch (final InvocationTargetException e) { 112 LOGGER.warn("Unable to invoke registerService method", e); 113 } 114 return serviceInfo; 115 } 116 LOGGER.warn("JMDNS not available - will not advertise ZeroConf support"); 117 return null; 118 } 119 120 /** 121 * Unadvertise the previously advertised entity 122 * 123 * @param serviceInfo 124 */ 125 @Override 126 public void unadvertise(final Object serviceInfo) { 127 if (jmDNS != null) { 128 try { 129 final Method method = jmDNSClass.getMethod("unregisterService", serviceInfoClass); 130 method.invoke(jmDNS, serviceInfo); 131 } catch (final IllegalAccessException e) { 132 LOGGER.warn("Unable to invoke unregisterService method", e); 133 } catch (final NoSuchMethodException e) { 134 LOGGER.warn("No unregisterService method", e); 135 } catch (final InvocationTargetException e) { 136 LOGGER.warn("Unable to invoke unregisterService method", e); 137 } 138 } 139 } 140 141 private static Object createJmDnsVersion1() { 142 try { 143 return jmDNSClass.getConstructor().newInstance(); 144 } catch (final InstantiationException e) { 145 LOGGER.warn("Unable to instantiate JMDNS", e); 146 } catch (final IllegalAccessException e) { 147 LOGGER.warn("Unable to instantiate JMDNS", e); 148 } catch (final NoSuchMethodException e) { 149 LOGGER.warn("Unable to instantiate JMDNS", e); 150 } catch (final InvocationTargetException e) { 151 LOGGER.warn("Unable to instantiate JMDNS", e); 152 } 153 return null; 154 } 155 156 private static Object createJmDnsVersion3() { 157 try { 158 final Method jmDNSCreateMethod = jmDNSClass.getMethod("create"); 159 return jmDNSCreateMethod.invoke(null, (Object[]) null); 160 } catch (final IllegalAccessException e) { 161 LOGGER.warn("Unable to invoke create method", e); 162 } catch (final NoSuchMethodException e) { 163 LOGGER.warn("Unable to get create method", e); 164 } catch (final InvocationTargetException e) { 165 LOGGER.warn("Unable to invoke create method", e); 166 } 167 return null; 168 } 169 170 private static Object buildServiceInfoVersion1(final String zone, final int port, final String name, 171 final Map<String, String> properties) { 172 // version 1 uses a hashtable 173 @SuppressWarnings("UseOfObsoleteCollectionType") 174 final Hashtable<String, String> hashtableProperties = new Hashtable<>(properties); 175 try { 176 return serviceInfoClass.getConstructor(String.class, String.class, int.class, int.class, int.class, 177 Hashtable.class).newInstance(zone, name, port, 0, 0, hashtableProperties); 178 } catch (final IllegalAccessException e) { 179 LOGGER.warn("Unable to construct ServiceInfo instance", e); 180 } catch (final NoSuchMethodException e) { 181 LOGGER.warn("Unable to get ServiceInfo constructor", e); 182 } catch (final InstantiationException e) { 183 LOGGER.warn("Unable to construct ServiceInfo instance", e); 184 } catch (final InvocationTargetException e) { 185 LOGGER.warn("Unable to construct ServiceInfo instance", e); 186 } 187 return null; 188 } 189 190 private static Object buildServiceInfoVersion3(final String zone, final int port, final String name, 191 final Map<String, String> properties) { 192 try { 193 return serviceInfoClass 194 // zone/type display name port weight priority properties 195 .getMethod("create", String.class, String.class, int.class, int.class, int.class, Map.class) 196 .invoke(null, zone, name, port, 0, 0, properties); 197 } catch (final IllegalAccessException e) { 198 LOGGER.warn("Unable to invoke create method", e); 199 } catch (final NoSuchMethodException e) { 200 LOGGER.warn("Unable to find create method", e); 201 } catch (final InvocationTargetException e) { 202 LOGGER.warn("Unable to invoke create method", e); 203 } 204 return null; 205 } 206 207 private static Object initializeJmDns() { 208 try { 209 jmDNSClass = Loader.loadClass("javax.jmdns.JmDNS"); 210 serviceInfoClass = Loader.loadClass("javax.jmdns.ServiceInfo"); 211 // if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API 212 boolean isVersion3 = false; 213 try { 214 // create method is in version 3, not version 1 215 jmDNSClass.getMethod("create"); 216 isVersion3 = true; 217 } catch (final NoSuchMethodException e) { 218 // no-op 219 } 220 221 if (isVersion3) { 222 return createJmDnsVersion3(); 223 } 224 return createJmDnsVersion1(); 225 } catch (final ClassNotFoundException e) { 226 LOGGER.warn("JmDNS or serviceInfo class not found", e); 227 } catch (final ExceptionInInitializerError e2) { 228 LOGGER.warn("JmDNS or serviceInfo class not found", e2); 229 } 230 return null; 231 } 232}