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