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.InvocationTargetException;
020 import java.lang.reflect.Method;
021 import java.util.HashMap;
022 import java.util.Hashtable;
023 import java.util.Map;
024
025 import org.apache.logging.log4j.Logger;
026 import org.apache.logging.log4j.core.config.plugins.Plugin;
027 import org.apache.logging.log4j.core.util.Integers;
028 import org.apache.logging.log4j.core.util.Loader;
029 import 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)
039 public 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 }