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.Constructor;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.HashMap;
23  import java.util.Hashtable;
24  import java.util.Map;
25  
26  import org.apache.logging.log4j.Logger;
27  import org.apache.logging.log4j.core.config.plugins.Plugin;
28  import org.apache.logging.log4j.core.helpers.Integers;
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", (Class[])null);
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", new Class[]{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         else
113         {
114             LOGGER.warn("JMDNS not available - will not advertise ZeroConf support");
115             return null;
116         }
117     }
118 
119     /**
120      * Unadvertise the previously advertised entity
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", new Class[]{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     {
141         try {
142             return jmDNSClass.newInstance();
143         } catch (final InstantiationException e) {
144             LOGGER.warn("Unable to instantiate JMDNS", e);
145         } catch (final IllegalAccessException e) {
146             LOGGER.warn("Unable to instantiate JMDNS", e);
147         }
148         return null;
149     }
150 
151     private static Object createJmDNSVersion3()
152     {
153         try {
154             final Method jmDNSCreateMethod = jmDNSClass.getMethod("create", (Class[])null);
155             return jmDNSCreateMethod.invoke(null, (Object[])null);
156         } catch (final IllegalAccessException e) {
157             LOGGER.warn("Unable to instantiate jmdns class", e);
158         } catch (final NoSuchMethodException e) {
159             LOGGER.warn("Unable to access constructor", e);
160         } catch (final InvocationTargetException e) {
161             LOGGER.warn("Unable to call constructor", e);
162         }
163         return null;
164     }
165 
166     private Object buildServiceInfoVersion1(final String zone, final int port, final String name, final Map<String, String> properties) {
167         //version 1 uses a hashtable
168         final Hashtable<String, String> hashtableProperties = new Hashtable<String, String>(properties);
169         try {
170             final Class<?>[] args = new Class<?>[6];
171             args[0] = String.class;
172             args[1] = String.class;
173             args[2] = int.class;
174             args[3] = int.class; //weight (0)
175             args[4] = int.class; //priority (0)
176             args[5] = Hashtable.class;
177             final Constructor<?> constructor  = serviceInfoClass.getConstructor(args);
178             final Object[] values = new Object[6];
179             values[0] = zone;
180             values[1] = name;
181             values[2] = port;
182             values[3] = 0;
183             values[4] = 0;
184             values[5] = hashtableProperties;
185             return constructor.newInstance(values);
186         } catch (final IllegalAccessException e) {
187             LOGGER.warn("Unable to construct ServiceInfo instance", e);
188         } catch (final NoSuchMethodException e) {
189             LOGGER.warn("Unable to get ServiceInfo constructor", e);
190         } catch (final InstantiationException e) {
191             LOGGER.warn("Unable to construct ServiceInfo instance", e);
192         } catch (final InvocationTargetException e) {
193             LOGGER.warn("Unable to construct ServiceInfo instance", e);
194         }
195         return null;
196     }
197 
198     private Object buildServiceInfoVersion3(final String zone, final int port, final String name, final Map<String, String> properties) {
199         try {
200             final Class<?>[] args = new Class<?>[6];
201             args[0] = String.class; //zone/type
202             args[1] = String.class; //display name
203             args[2] = int.class; //port
204             args[3] = int.class; //weight (0)
205             args[4] = int.class; //priority (0)
206             args[5] = Map.class;
207             final Method serviceInfoCreateMethod = serviceInfoClass.getMethod("create", args);
208             final Object[] values = new Object[6];
209             values[0] = zone;
210             values[1] = name;
211             values[2] = port;
212             values[3] = 0;
213             values[4] = 0;
214             values[5] = properties;
215             return serviceInfoCreateMethod.invoke(null, values);
216         } catch (final IllegalAccessException e) {
217             LOGGER.warn("Unable to invoke create method", e);
218         } catch (final NoSuchMethodException e) {
219             LOGGER.warn("Unable to find create method", e);
220         } catch (final InvocationTargetException e) {
221             LOGGER.warn("Unable to invoke create method", e);
222         }
223         return null;
224     }
225 
226     private static Object initializeJMDNS() {
227         try {
228             jmDNSClass = Class.forName("javax.jmdns.JmDNS");
229             serviceInfoClass = Class.forName("javax.jmdns.ServiceInfo");
230             //if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API
231             boolean isVersion3 = false;
232             try {
233                 //create method is in version 3, not version 1
234                 jmDNSClass.getMethod("create", (Class[])null);
235                 isVersion3 = true;
236             } catch (final NoSuchMethodException e) {
237                 //no-op
238             }
239 
240             if (isVersion3) {
241                 return createJmDNSVersion3();
242             } else {
243                 return createJmDNSVersion1();
244             }
245         } catch (final ClassNotFoundException e) {
246             LOGGER.warn("JmDNS or serviceInfo class not found", e);
247         } catch (final ExceptionInInitializerError e2) {
248             LOGGER.warn("JmDNS or serviceInfo class not found", e2);
249         }
250         return null;
251     }
252 }