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    }