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.Constructor;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import java.util.Hashtable;
023    import java.util.Map;
024    import org.apache.logging.log4j.Logger;
025    import org.apache.logging.log4j.core.config.plugins.Plugin;
026    import org.apache.logging.log4j.status.StatusLogger;
027    
028    /**
029     * Advertise an entity via ZeroConf/MulticastDNS and the JmDNS library.
030     *
031     */
032    @Plugin(name = "multicastdns", type = "Core", elementType = "advertiser", printObject = false)
033    public class MulticastDNSAdvertiser implements Advertiser {
034        protected static final Logger LOGGER = StatusLogger.getLogger();
035        private static Object jmDNS = initializeJMDNS();
036    
037        private static Class<?> jmDNSClass;
038        private static Class<?> serviceInfoClass;
039    
040        public MulticastDNSAdvertiser()
041        {
042            //no arg constructor for reflection 
043        }
044    
045        /**
046         * Advertise the provided entity.
047         * 
048         * Properties map provided in advertise method must include a "name" entry 
049         * but may also provide "protocol" (tcp/udp) as well as a "port" entry
050         * 
051         * @param properties the properties representing the entity to advertise
052         * @return the object which can be used to unadvertise, or null if advertisement was unsuccessful
053         */
054        public Object advertise(Map<String, String> properties) {
055            //default to tcp if "protocol" was not set
056            String protocol = properties.get("protocol");
057            String zone = "._log4j._"+(protocol != null ? protocol : "tcp") + ".local.";
058            //default to 4555 if "port" was not set
059            String portString = properties.get("port");
060            int port = (portString != null ? Integer.parseInt(portString) : 4555);
061    
062            String name = properties.get("name");
063    
064            //if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
065            if (jmDNS != null)
066            {
067                boolean isVersion3 = false;
068                try {
069                    //create method is in version 3, not version 1
070                    jmDNSClass.getMethod("create", (Class[])null);
071                    isVersion3 = true;
072                } catch (NoSuchMethodException e) {
073                    //no-op
074                }
075                System.out.println("building: " + zone);
076                Object serviceInfo;
077                if (isVersion3) {
078                    serviceInfo = buildServiceInfoVersion3(zone, port, name, properties);
079                } else {
080                    serviceInfo = buildServiceInfoVersion1(zone, port, name, properties);
081                }
082    
083                try {
084                    Method method = jmDNSClass.getMethod("registerService", new Class[]{serviceInfoClass});
085                    method.invoke(jmDNS, serviceInfo);
086                } catch(IllegalAccessException e) {
087                    LOGGER.warn("Unable to invoke registerService method", e);
088                } catch(NoSuchMethodException e) {
089                    LOGGER.warn("No registerService method", e);
090                } catch(InvocationTargetException e) {
091                    LOGGER.warn("Unable to invoke registerService method", e);
092                }
093                return serviceInfo;
094            }
095            else
096            {
097                LOGGER.warn("JMDNS not available - will not advertise ZeroConf support");
098                return null;
099            }
100        }
101    
102        /**
103         * Unadvertise the previously advertised entity
104         * @param serviceInfo
105         */
106        public void unadvertise(Object serviceInfo) {
107            if (jmDNS != null) {
108                try {
109                    Method method = jmDNSClass.getMethod("unregisterService", new Class[]{serviceInfoClass});
110                    method.invoke(jmDNS, serviceInfo);
111                } catch(IllegalAccessException e) {
112                    LOGGER.warn("Unable to invoke unregisterService method", e);
113                } catch(NoSuchMethodException e) {
114                    LOGGER.warn("No unregisterService method", e);
115                } catch(InvocationTargetException e) {
116                    LOGGER.warn("Unable to invoke unregisterService method", e);
117                }
118            }
119        }
120    
121        private static Object createJmDNSVersion1()
122        {
123            try {
124                return jmDNSClass.newInstance();
125            } catch (InstantiationException e) {
126                LOGGER.warn("Unable to instantiate JMDNS", e);
127            } catch (IllegalAccessException e) {
128                LOGGER.warn("Unable to instantiate JMDNS", e);
129            }
130            return null;
131        }
132    
133        private static Object createJmDNSVersion3()
134        {
135            try {
136                Method jmDNSCreateMethod = jmDNSClass.getMethod("create", (Class[])null);
137                return jmDNSCreateMethod.invoke(null, (Object[])null);
138            } catch (IllegalAccessException e) {
139                LOGGER.warn("Unable to instantiate jmdns class", e);
140            } catch (NoSuchMethodException e) {
141                LOGGER.warn("Unable to access constructor", e);
142            } catch (InvocationTargetException e) {
143                LOGGER.warn("Unable to call constructor", e);
144            }
145            return null;
146        }
147    
148        private Object buildServiceInfoVersion1(String zone, int port, String name, Map<String, String> properties) {
149            //version 1 uses a hashtable
150            Hashtable<String, String> hashtableProperties = new Hashtable<String, String>(properties);
151            try {
152                Class[] args = new Class[6];
153                args[0] = String.class;
154                args[1] = String.class;
155                args[2] = int.class;
156                args[3] = int.class; //weight (0)
157                args[4] = int.class; //priority (0)
158                args[5] = Hashtable.class;
159                Constructor<?> constructor  = serviceInfoClass.getConstructor(args);
160                Object[] values = new Object[6];
161                values[0] = zone;
162                values[1] = name;
163                values[2] = port;
164                values[3] = 0;
165                values[4] = 0;
166                values[5] = hashtableProperties;
167                return constructor.newInstance(values);
168            } catch (IllegalAccessException e) {
169                LOGGER.warn("Unable to construct ServiceInfo instance", e);
170            } catch (NoSuchMethodException e) {
171                LOGGER.warn("Unable to get ServiceInfo constructor", e);
172            } catch (InstantiationException e) {
173                LOGGER.warn("Unable to construct ServiceInfo instance", e);
174            } catch (InvocationTargetException e) {
175                LOGGER.warn("Unable to construct ServiceInfo instance", e);
176            }
177            return null;
178        }
179    
180        private Object buildServiceInfoVersion3(String zone, int port, String name, Map<String, String> properties) {
181            try {
182                Class[] args = new Class[6];
183                args[0] = String.class; //zone/type
184                args[1] = String.class; //display name
185                args[2] = int.class; //port
186                args[3] = int.class; //weight (0)
187                args[4] = int.class; //priority (0)
188                args[5] = Map.class;
189                Method serviceInfoCreateMethod = serviceInfoClass.getMethod("create", args);
190                Object[] values = new Object[6];
191                values[0] = zone;
192                values[1] = name;
193                values[2] = port;
194                values[3] = 0;
195                values[4] = 0;
196                values[5] = properties;
197                return serviceInfoCreateMethod.invoke(null, values);
198            } catch (IllegalAccessException e) {
199                LOGGER.warn("Unable to invoke create method", e);
200            } catch (NoSuchMethodException e) {
201                LOGGER.warn("Unable to find create method", e);
202            } catch (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 = Class.forName("javax.jmdns.JmDNS");
211                serviceInfoClass = Class.forName("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", (Class[])null);
217                    isVersion3 = true;
218                } catch (NoSuchMethodException e) {
219                    //no-op
220                }
221    
222                if (isVersion3) {
223                    return createJmDNSVersion3();
224                } else {
225                    return createJmDNSVersion1();
226                }
227            } catch (ClassNotFoundException e) {
228                LOGGER.warn("JmDNS or serviceInfo class not found", e);
229            } catch (ExceptionInInitializerError e2) {
230                LOGGER.warn("JmDNS or serviceInfo class not found", e2);
231            }
232            return null;
233        }
234    }