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 | InvocationTargetException e) { 106 LOGGER.warn("Unable to invoke registerService method", e); 107 } catch (final NoSuchMethodException e) { 108 LOGGER.warn("No 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 * 119 * @param serviceInfo 120 */ 121 @Override 122 public void unadvertise(final Object serviceInfo) { 123 if (jmDNS != null) { 124 try { 125 final Method method = jmDNSClass.getMethod("unregisterService", serviceInfoClass); 126 method.invoke(jmDNS, serviceInfo); 127 } catch (final IllegalAccessException | InvocationTargetException e) { 128 LOGGER.warn("Unable to invoke unregisterService method", e); 129 } catch (final NoSuchMethodException e) { 130 LOGGER.warn("No unregisterService method", e); 131 } 132 } 133 } 134 135 private static Object createJmDnsVersion1() { 136 try { 137 return jmDNSClass.getConstructor().newInstance(); 138 } catch (final InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 139 LOGGER.warn("Unable to instantiate JMDNS", e); 140 } 141 return null; 142 } 143 144 private static Object createJmDnsVersion3() { 145 try { 146 final Method jmDNSCreateMethod = jmDNSClass.getMethod("create"); 147 return jmDNSCreateMethod.invoke(null, (Object[]) null); 148 } catch (final IllegalAccessException | InvocationTargetException e) { 149 LOGGER.warn("Unable to invoke create method", e); 150 } catch (final NoSuchMethodException e) { 151 LOGGER.warn("Unable to get create method", e); 152 } 153 return null; 154 } 155 156 private static Object buildServiceInfoVersion1(final String zone, final int port, final String name, 157 final Map<String, String> properties) { 158 // version 1 uses a hashtable 159 @SuppressWarnings("UseOfObsoleteCollectionType") 160 final Hashtable<String, String> hashtableProperties = new Hashtable<>(properties); 161 try { 162 return serviceInfoClass.getConstructor(String.class, String.class, int.class, int.class, int.class, 163 Hashtable.class).newInstance(zone, name, port, 0, 0, hashtableProperties); 164 } catch (final IllegalAccessException | InstantiationException | InvocationTargetException e) { 165 LOGGER.warn("Unable to construct ServiceInfo instance", e); 166 } catch (final NoSuchMethodException e) { 167 LOGGER.warn("Unable to get ServiceInfo constructor", e); 168 } 169 return null; 170 } 171 172 private static Object buildServiceInfoVersion3(final String zone, final int port, final String name, 173 final Map<String, String> properties) { 174 try { 175 return serviceInfoClass 176 // zone/type display name port weight priority properties 177 .getMethod("create", String.class, String.class, int.class, int.class, int.class, Map.class) 178 .invoke(null, zone, name, port, 0, 0, properties); 179 } catch (final IllegalAccessException | InvocationTargetException e) { 180 LOGGER.warn("Unable to invoke create method", e); 181 } catch (final NoSuchMethodException e) { 182 LOGGER.warn("Unable to find create method", e); 183 } 184 return null; 185 } 186 187 private static Object initializeJmDns() { 188 try { 189 jmDNSClass = Loader.loadClass("javax.jmdns.JmDNS"); 190 serviceInfoClass = Loader.loadClass("javax.jmdns.ServiceInfo"); 191 // if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API 192 boolean isVersion3 = false; 193 try { 194 // create method is in version 3, not version 1 195 jmDNSClass.getMethod("create"); 196 isVersion3 = true; 197 } catch (final NoSuchMethodException e) { 198 // no-op 199 } 200 201 if (isVersion3) { 202 return createJmDnsVersion3(); 203 } 204 return createJmDnsVersion1(); 205 } catch (final ClassNotFoundException | ExceptionInInitializerError e) { 206 LOGGER.warn("JmDNS or serviceInfo class not found", e); 207 } 208 return null; 209 } 210}