    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.logging.log4j.core.net;
    018
    019    import java.lang.reflect.InvocationTargetException;
    020    import java.lang.reflect.Method;
    021    import java.util.HashMap;
    022    import java.util.Hashtable;
    023    import java.util.Map;
    024
    025    import org.apache.logging.log4j.Logger;
    026    import org.apache.logging.log4j.core.config.plugins.Plugin;
    027    import org.apache.logging.log4j.core.util.Integers;
    028    import org.apache.logging.log4j.core.util.Loader;
    029    import org.apache.logging.log4j.status.StatusLogger;
    030
    031    /**
    032     * Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library.
    033     *
    034     * The length of property names and values must be 255 bytes or less.
    035     * Entries with names or values larger than 255 bytes will be removed prior to advertisement.
    036     *
    037     */
    038    @Plugin(name = "multicastdns", category = "Core", elementType = "advertiser", printObject = false)
    039    public class MulticastDnsAdvertiser implements Advertiser {
    040        protected static final Logger LOGGER = StatusLogger.getLogger();
    041        private static Object jmDNS = initializeJmDns();
    042
    043        private static Class<?> jmDNSClass;
    044        private static Class<?> serviceInfoClass;
    045
    046        public MulticastDnsAdvertiser()
    047        {
    048            //no arg constructor for reflection
    049        }
    050
    051        /**
    052         * Advertise the provided entity.
    053         *
    054         * Properties map provided in advertise method must include a "name" entry
    055         * but may also provide "protocol" (tcp/udp) as well as a "port" entry
    056         *
    057         * The length of property names and values must be 255 bytes or less.
    058         * Entries with names or values larger than 255 bytes will be removed prior to advertisement.
    059         *
    060         * @param properties the properties representing the entity to advertise
    061         * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful
    062         */
    063        @Override
    064        public Object advertise(final Map<String, String> properties) {
    065            //default to tcp if "protocol" was not set
    066            final Map<String, String> truncatedProperties = new HashMap<String, String>();
    067            for (final Map.Entry<String, String> entry:properties.entrySet())
    068            {
    069                if (entry.getKey().length() <= 255 && entry.getValue().length() <= 255)
    070                {
    071                    truncatedProperties.put(entry.getKey(), entry.getValue());
    072                }
    073            }
    074            final String protocol = truncatedProperties.get("protocol");
    075            final String zone = "._log4j._"+(protocol != null ? protocol : "tcp") + ".local.";
    076            //default to 4555 if "port" was not set
    077            final String portString = truncatedProperties.get("port");
    078            final int port = Integers.parseInt(portString, 4555);
    079
    080            final String name = truncatedProperties.get("name");
    081
    082            //if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
    083            if (jmDNS != null)
    084            {
    085                boolean isVersion3 = false;
    086                try {
    087                    //create method is in version 3, not version 1
    088                    jmDNSClass.getMethod("create");
    089                    isVersion3 = true;
    090                } catch (final NoSuchMethodException e) {
    091                    //no-op
    092                }
    093                Object serviceInfo;
    094                if (isVersion3) {
    095                    serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties);
    096                } else {
    097                    serviceInfo = buildServiceInfoVersion1(zone, port, name, truncatedProperties);
    098                }
    099
    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    }