View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.net;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.util.HashMap;
22  import java.util.Hashtable;
23  import java.util.Map;
24  
25  import org.apache.logging.log4j.Logger;
26  import org.apache.logging.log4j.core.config.plugins.Plugin;
27  import org.apache.logging.log4j.core.util.Integers;
28  import org.apache.logging.log4j.core.util.Loader;
29  import org.apache.logging.log4j.status.StatusLogger;
30  
31  /**
32   * Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library.
33   *
34   * The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255 bytes
35   * will be removed prior to advertisement.
36   *
37   */
38  @Plugin(name = "multicastdns", category = "Core", elementType = "advertiser", printObject = false)
39  public class MulticastDnsAdvertiser implements Advertiser {
40      /**
41       * Status logger.
42       */
43      protected static final Logger LOGGER = StatusLogger.getLogger();
44  
45      private static final int MAX_LENGTH = 255;
46      private static final int DEFAULT_PORT = 4555;
47  
48      private static Object jmDNS = initializeJmDns();
49      private static Class<?> jmDNSClass;
50      private static Class<?> serviceInfoClass;
51  
52      public MulticastDnsAdvertiser() {
53          // no arg constructor for reflection
54      }
55  
56      /**
57       * Advertise the provided entity.
58       *
59       * Properties map provided in advertise method must include a "name" entry but may also provide "protocol" (tcp/udp)
60       * as well as a "port" entry
61       *
62       * The length of property names and values must be 255 bytes or less. Entries with names or values larger than 255
63       * bytes will be removed prior to advertisement.
64       *
65       * @param properties the properties representing the entity to advertise
66       * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful
67       */
68      @Override
69      public Object advertise(final Map<String, String> properties) {
70          // default to tcp if "protocol" was not set
71          final Map<String, String> truncatedProperties = new HashMap<>();
72          for (final Map.Entry<String, String> entry : properties.entrySet()) {
73              if (entry.getKey().length() <= MAX_LENGTH && entry.getValue().length() <= MAX_LENGTH) {
74                  truncatedProperties.put(entry.getKey(), entry.getValue());
75              }
76          }
77          final String protocol = truncatedProperties.get("protocol");
78          final String zone = "._log4j._" + (protocol != null ? protocol : "tcp") + ".local.";
79          // default to 4555 if "port" was not set
80          final String portString = truncatedProperties.get("port");
81          final int port = Integers.parseInt(portString, DEFAULT_PORT);
82  
83          final String name = truncatedProperties.get("name");
84  
85          // if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
86          if (jmDNS != null) {
87              boolean isVersion3 = false;
88              try {
89                  // create method is in version 3, not version 1
90                  jmDNSClass.getMethod("create");
91                  isVersion3 = true;
92              } catch (final NoSuchMethodException e) {
93                  // no-op
94              }
95              Object serviceInfo;
96              if (isVersion3) {
97                  serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties);
98              } else {
99                  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 e) {
106                 LOGGER.warn("Unable to invoke registerService method", e);
107             } catch (final NoSuchMethodException e) {
108                 LOGGER.warn("No registerService method", e);
109             } catch (final InvocationTargetException e) {
110                 LOGGER.warn("Unable to invoke registerService method", e);
111             }
112             return serviceInfo;
113         }
114         LOGGER.warn("JMDNS not available - will not advertise ZeroConf support");
115         return null;
116     }
117 
118     /**
119      * Unadvertise the previously advertised entity.
120      * 
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", 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         try {
141             return jmDNSClass.getConstructor().newInstance();
142         } catch (final InstantiationException e) {
143             LOGGER.warn("Unable to instantiate JMDNS", e);
144         } catch (final IllegalAccessException e) {
145             LOGGER.warn("Unable to instantiate JMDNS", e);
146         } catch (final NoSuchMethodException e) {
147             LOGGER.warn("Unable to instantiate JMDNS", e);
148         } catch (final InvocationTargetException e) {
149             LOGGER.warn("Unable to instantiate JMDNS", e);
150         }
151         return null;
152     }
153 
154     private static Object createJmDnsVersion3() {
155         try {
156             final Method jmDNSCreateMethod = jmDNSClass.getMethod("create");
157             return jmDNSCreateMethod.invoke(null, (Object[]) null);
158         } catch (final IllegalAccessException e) {
159             LOGGER.warn("Unable to invoke create method", e);
160         } catch (final NoSuchMethodException e) {
161             LOGGER.warn("Unable to get create method", e);
162         } catch (final InvocationTargetException e) {
163             LOGGER.warn("Unable to invoke create method", e);
164         }
165         return null;
166     }
167 
168     private static Object buildServiceInfoVersion1(final String zone, final int port, final String name,
169             final Map<String, String> properties) {
170         // version 1 uses a hashtable
171         @SuppressWarnings("UseOfObsoleteCollectionType")
172         final Hashtable<String, String> hashtableProperties = new Hashtable<>(properties);
173         try {
174             return serviceInfoClass.getConstructor(String.class, String.class, int.class, int.class, int.class,
175                     Hashtable.class).newInstance(zone, name, port, 0, 0, hashtableProperties);
176         } catch (final IllegalAccessException e) {
177             LOGGER.warn("Unable to construct ServiceInfo instance", e);
178         } catch (final NoSuchMethodException e) {
179             LOGGER.warn("Unable to get ServiceInfo constructor", e);
180         } catch (final InstantiationException e) {
181             LOGGER.warn("Unable to construct ServiceInfo instance", e);
182         } catch (final InvocationTargetException e) {
183             LOGGER.warn("Unable to construct ServiceInfo instance", e);
184         }
185         return null;
186     }
187 
188     private static Object buildServiceInfoVersion3(final String zone, final int port, final String name,
189             final Map<String, String> properties) {
190         try {
191             return serviceInfoClass
192                     // zone/type display name port weight priority properties
193                     .getMethod("create", String.class, String.class, int.class, int.class, int.class, Map.class)
194                     .invoke(null, zone, name, port, 0, 0, properties);
195         } catch (final IllegalAccessException e) {
196             LOGGER.warn("Unable to invoke create method", e);
197         } catch (final NoSuchMethodException e) {
198             LOGGER.warn("Unable to find create method", e);
199         } catch (final InvocationTargetException e) {
200             LOGGER.warn("Unable to invoke create method", e);
201         }
202         return null;
203     }
204 
205     private static Object initializeJmDns() {
206         try {
207             jmDNSClass = Loader.loadClass("javax.jmdns.JmDNS");
208             serviceInfoClass = Loader.loadClass("javax.jmdns.ServiceInfo");
209             // if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API
210             boolean isVersion3 = false;
211             try {
212                 // create method is in version 3, not version 1
213                 jmDNSClass.getMethod("create");
214                 isVersion3 = true;
215             } catch (final NoSuchMethodException e) {
216                 // no-op
217             }
218 
219             if (isVersion3) {
220                 return createJmDnsVersion3();
221             }
222             return createJmDnsVersion1();
223         } catch (final ClassNotFoundException e) {
224             LOGGER.warn("JmDNS or serviceInfo class not found", e);
225         } catch (final ExceptionInInitializerError e2) {
226             LOGGER.warn("JmDNS or serviceInfo class not found", e2);
227         }
228         return null;
229     }
230 }