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