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  import org.apache.logging.log4j.Logger;
26  import org.apache.logging.log4j.core.config.plugins.Plugin;
27  import org.apache.logging.log4j.status.StatusLogger;
28  
29  /**
30   * Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library.
31   *
32   * The length of property names and values must be 255 bytes or less.  
33   * Entries with names or values larger than 255 bytes will be removed prior to advertisement.
34   *
35   */
36  @Plugin(name = "multicastdns", category = "Core", elementType = "advertiser", printObject = false)
37  public class MulticastDNSAdvertiser implements Advertiser {
38      protected static final Logger LOGGER = StatusLogger.getLogger();
39      private static Object jmDNS = initializeJMDNS();
40  
41      private static Class<?> jmDNSClass;
42      private static Class<?> serviceInfoClass;
43  
44      public MulticastDNSAdvertiser()
45      {
46          //no arg constructor for reflection
47      }
48  
49      /**
50       * Advertise the provided entity.
51       *
52       * Properties map provided in advertise method must include a "name" entry
53       * but may also provide "protocol" (tcp/udp) as well as a "port" entry
54       *
55       * The length of property names and values must be 255 bytes or less.  
56       * Entries with names or values larger than 255 bytes will be removed prior to advertisement.
57       * 
58       * @param properties the properties representing the entity to advertise
59       * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful
60       */
61      @Override
62      public Object advertise(Map<String, String> properties) {
63          //default to tcp if "protocol" was not set
64          Map<String, String> truncatedProperties = new HashMap<String, String>();
65          for (Map.Entry<String, String> entry:properties.entrySet())
66          {
67              if (entry.getKey().length() <= 255 && entry.getValue().length() <= 255)
68              {
69                  truncatedProperties.put(entry.getKey(), entry.getValue());
70              }
71          }
72          String protocol = truncatedProperties.get("protocol");
73          String zone = "._log4j._"+(protocol != null ? protocol : "tcp") + ".local.";
74          //default to 4555 if "port" was not set
75          String portString = truncatedProperties.get("port");
76          int port = (portString != null ? Integer.parseInt(portString) : 4555);
77  
78          String name = truncatedProperties.get("name");
79  
80          //if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
81          if (jmDNS != null)
82          {
83              boolean isVersion3 = false;
84              try {
85                  //create method is in version 3, not version 1
86                  jmDNSClass.getMethod("create", (Class[])null);
87                  isVersion3 = true;
88              } catch (NoSuchMethodException e) {
89                  //no-op
90              }
91              Object serviceInfo;
92              if (isVersion3) {
93                  serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties);
94              } else {
95                  serviceInfo = buildServiceInfoVersion1(zone, port, name, truncatedProperties);
96              }
97  
98              try {
99                  Method method = jmDNSClass.getMethod("registerService", new Class[]{serviceInfoClass});
100                 method.invoke(jmDNS, serviceInfo);
101             } catch(IllegalAccessException e) {
102                 LOGGER.warn("Unable to invoke registerService method", e);
103             } catch(NoSuchMethodException e) {
104                 LOGGER.warn("No registerService method", e);
105             } catch(InvocationTargetException e) {
106                 LOGGER.warn("Unable to invoke registerService method", e);
107             }
108             return serviceInfo;
109         }
110         else
111         {
112             LOGGER.warn("JMDNS not available - will not advertise ZeroConf support");
113             return null;
114         }
115     }
116 
117     /**
118      * Unadvertise the previously advertised entity
119      * @param serviceInfo
120      */
121     @Override
122     public void unadvertise(Object serviceInfo) {
123         if (jmDNS != null) {
124             try {
125                 Method method = jmDNSClass.getMethod("unregisterService", new Class[]{serviceInfoClass});
126                 method.invoke(jmDNS, serviceInfo);
127             } catch(IllegalAccessException e) {
128                 LOGGER.warn("Unable to invoke unregisterService method", e);
129             } catch(NoSuchMethodException e) {
130                 LOGGER.warn("No unregisterService method", e);
131             } catch(InvocationTargetException e) {
132                 LOGGER.warn("Unable to invoke unregisterService method", e);
133             }
134         }
135     }
136 
137     private static Object createJmDNSVersion1()
138     {
139         try {
140             return jmDNSClass.newInstance();
141         } catch (InstantiationException e) {
142             LOGGER.warn("Unable to instantiate JMDNS", e);
143         } catch (IllegalAccessException e) {
144             LOGGER.warn("Unable to instantiate JMDNS", e);
145         }
146         return null;
147     }
148 
149     private static Object createJmDNSVersion3()
150     {
151         try {
152             Method jmDNSCreateMethod = jmDNSClass.getMethod("create", (Class[])null);
153             return jmDNSCreateMethod.invoke(null, (Object[])null);
154         } catch (IllegalAccessException e) {
155             LOGGER.warn("Unable to instantiate jmdns class", e);
156         } catch (NoSuchMethodException e) {
157             LOGGER.warn("Unable to access constructor", e);
158         } catch (InvocationTargetException e) {
159             LOGGER.warn("Unable to call constructor", e);
160         }
161         return null;
162     }
163 
164     private Object buildServiceInfoVersion1(String zone, int port, String name, Map<String, String> properties) {
165         //version 1 uses a hashtable
166         Hashtable<String, String> hashtableProperties = new Hashtable<String, String>(properties);
167         try {
168             Class[] args = new Class[6];
169             args[0] = String.class;
170             args[1] = String.class;
171             args[2] = int.class;
172             args[3] = int.class; //weight (0)
173             args[4] = int.class; //priority (0)
174             args[5] = Hashtable.class;
175             Constructor<?> constructor  = serviceInfoClass.getConstructor(args);
176             Object[] values = new Object[6];
177             values[0] = zone;
178             values[1] = name;
179             values[2] = port;
180             values[3] = 0;
181             values[4] = 0;
182             values[5] = hashtableProperties;
183             return constructor.newInstance(values);
184         } catch (IllegalAccessException e) {
185             LOGGER.warn("Unable to construct ServiceInfo instance", e);
186         } catch (NoSuchMethodException e) {
187             LOGGER.warn("Unable to get ServiceInfo constructor", e);
188         } catch (InstantiationException e) {
189             LOGGER.warn("Unable to construct ServiceInfo instance", e);
190         } catch (InvocationTargetException e) {
191             LOGGER.warn("Unable to construct ServiceInfo instance", e);
192         }
193         return null;
194     }
195 
196     private Object buildServiceInfoVersion3(String zone, int port, String name, Map<String, String> properties) {
197         try {
198             Class[] args = new Class[6];
199             args[0] = String.class; //zone/type
200             args[1] = String.class; //display name
201             args[2] = int.class; //port
202             args[3] = int.class; //weight (0)
203             args[4] = int.class; //priority (0)
204             args[5] = Map.class;
205             Method serviceInfoCreateMethod = serviceInfoClass.getMethod("create", args);
206             Object[] values = new Object[6];
207             values[0] = zone;
208             values[1] = name;
209             values[2] = port;
210             values[3] = 0;
211             values[4] = 0;
212             values[5] = properties;
213             return serviceInfoCreateMethod.invoke(null, values);
214         } catch (IllegalAccessException e) {
215             LOGGER.warn("Unable to invoke create method", e);
216         } catch (NoSuchMethodException e) {
217             LOGGER.warn("Unable to find create method", e);
218         } catch (InvocationTargetException e) {
219             LOGGER.warn("Unable to invoke create method", e);
220         }
221         return null;
222     }
223 
224     private static Object initializeJMDNS() {
225         try {
226             jmDNSClass = Class.forName("javax.jmdns.JmDNS");
227             serviceInfoClass = Class.forName("javax.jmdns.ServiceInfo");
228             //if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API
229             boolean isVersion3 = false;
230             try {
231                 //create method is in version 3, not version 1
232                 jmDNSClass.getMethod("create", (Class[])null);
233                 isVersion3 = true;
234             } catch (NoSuchMethodException e) {
235                 //no-op
236             }
237 
238             if (isVersion3) {
239                 return createJmDNSVersion3();
240             } else {
241                 return createJmDNSVersion1();
242             }
243         } catch (ClassNotFoundException e) {
244             LOGGER.warn("JmDNS or serviceInfo class not found", e);
245         } catch (ExceptionInInitializerError e2) {
246             LOGGER.warn("JmDNS or serviceInfo class not found", e2);
247         }
248         return null;
249     }
250 }