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 }