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.
35   * Entries with names or values larger than 255 bytes 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      protected static final Logger LOGGER = StatusLogger.getLogger();
41      private static Object jmDNS = initializeJmDns();
42  
43      private static Class<?> jmDNSClass;
44      private static Class<?> serviceInfoClass;
45  
46      public MulticastDnsAdvertiser()
47      {
48          //no arg constructor for reflection
49      }
50  
51      /**
52       * Advertise the provided entity.
53       *
54       * Properties map provided in advertise method must include a "name" entry
55       * but may also provide "protocol" (tcp/udp) as well as a "port" entry
56       *
57       * The length of property names and values must be 255 bytes or less.
58       * Entries with names or values larger than 255 bytes will be removed prior to advertisement.
59       *
60       * @param properties the properties representing the entity to advertise
61       * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful
62       */
63      @Override
64      public Object advertise(final Map<String, String> properties) {
65          //default to tcp if "protocol" was not set
66          final Map<String, String> truncatedProperties = new HashMap<String, String>();
67          for (final Map.Entry<String, String> entry:properties.entrySet())
68          {
69              if (entry.getKey().length() <= 255 && entry.getValue().length() <= 255)
70              {
71                  truncatedProperties.put(entry.getKey(), entry.getValue());
72              }
73          }
74          final String protocol = truncatedProperties.get("protocol");
75          final String zone = "._log4j._"+(protocol != null ? protocol : "tcp") + ".local.";
76          //default to 4555 if "port" was not set
77          final String portString = truncatedProperties.get("port");
78          final int port = Integers.parseInt(portString, 4555);
79  
80          final String name = truncatedProperties.get("name");
81  
82          //if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
83          if (jmDNS != null)
84          {
85              boolean isVersion3 = false;
86              try {
87                  //create method is in version 3, not version 1
88                  jmDNSClass.getMethod("create");
89                  isVersion3 = true;
90              } catch (final NoSuchMethodException e) {
91                  //no-op
92              }
93              Object serviceInfo;
94              if (isVersion3) {
95                  serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties);
96              } else {
97                  serviceInfo = buildServiceInfoVersion1(zone, port, name, truncatedProperties);
98              }
99  
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 }