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. 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"); 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", 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 LOGGER.warn("JMDNS not available - will not advertise ZeroConf support"); 113 return null; 114 } 115 116 /** 117 * Unadvertise the previously advertised entity 118 * @param serviceInfo 119 */ 120 @Override 121 public void unadvertise(final Object serviceInfo) { 122 if (jmDNS != null) { 123 try { 124 final Method method = jmDNSClass.getMethod("unregisterService", serviceInfoClass); 125 method.invoke(jmDNS, serviceInfo); 126 } catch(final IllegalAccessException e) { 127 LOGGER.warn("Unable to invoke unregisterService method", e); 128 } catch(final NoSuchMethodException e) { 129 LOGGER.warn("No unregisterService method", e); 130 } catch(final InvocationTargetException e) { 131 LOGGER.warn("Unable to invoke unregisterService method", e); 132 } 133 } 134 } 135 136 private static Object createJmDnsVersion1() 137 { 138 try { 139 return jmDNSClass.getConstructor().newInstance(); 140 } catch (final InstantiationException e) { 141 LOGGER.warn("Unable to instantiate JMDNS", e); 142 } catch (final IllegalAccessException e) { 143 LOGGER.warn("Unable to instantiate JMDNS", e); 144 } catch (final NoSuchMethodException e) { 145 LOGGER.warn("Unable to instantiate JMDNS", e); 146 } catch (final InvocationTargetException e) { 147 LOGGER.warn("Unable to instantiate JMDNS", e); 148 } 149 return null; 150 } 151 152 private static Object createJmDnsVersion3() 153 { 154 try { 155 final Method jmDNSCreateMethod = jmDNSClass.getMethod("create"); 156 return jmDNSCreateMethod.invoke(null, (Object[])null); 157 } catch (final IllegalAccessException e) { 158 LOGGER.warn("Unable to invoke create method", e); 159 } catch (final NoSuchMethodException e) { 160 LOGGER.warn("Unable to get create method", e); 161 } catch (final InvocationTargetException e) { 162 LOGGER.warn("Unable to invoke create method", e); 163 } 164 return null; 165 } 166 167 private static Object buildServiceInfoVersion1(final String zone, 168 final int port, 169 final String name, 170 final Map<String, String> properties) { 171 //version 1 uses a hashtable 172 @SuppressWarnings("UseOfObsoleteCollectionType") 173 final Hashtable<String, String> hashtableProperties = new Hashtable<String, String>(properties); 174 try { 175 return serviceInfoClass 176 .getConstructor(String.class, String.class, int.class, int.class, int.class, Hashtable.class) 177 .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, 191 final int port, 192 final String name, 193 final Map<String, String> properties) { 194 try { 195 return serviceInfoClass // zone/type display name port weight priority properties 196 .getMethod("create", String.class, String.class, int.class, int.class, int.class, Map.class) 197 .invoke(null, zone, name, port, 0, 0, properties); 198 } catch (final IllegalAccessException e) { 199 LOGGER.warn("Unable to invoke create method", e); 200 } catch (final NoSuchMethodException e) { 201 LOGGER.warn("Unable to find create method", e); 202 } catch (final InvocationTargetException e) { 203 LOGGER.warn("Unable to invoke create method", e); 204 } 205 return null; 206 } 207 208 private static Object initializeJmDns() { 209 try { 210 jmDNSClass = Loader.loadClass("javax.jmdns.JmDNS"); 211 serviceInfoClass = Loader.loadClass("javax.jmdns.ServiceInfo"); 212 //if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API 213 boolean isVersion3 = false; 214 try { 215 //create method is in version 3, not version 1 216 jmDNSClass.getMethod("create"); 217 isVersion3 = true; 218 } catch (final NoSuchMethodException e) { 219 //no-op 220 } 221 222 if (isVersion3) { 223 return createJmDnsVersion3(); 224 } 225 return createJmDnsVersion1(); 226 } catch (final ClassNotFoundException e) { 227 LOGGER.warn("JmDNS or serviceInfo class not found", e); 228 } catch (final ExceptionInInitializerError e2) { 229 LOGGER.warn("JmDNS or serviceInfo class not found", e2); 230 } 231 return null; 232 } 233}