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