1 /**
2  *
3  */
4 package javax.jmdns.impl;
5 
6 import java.io.IOException;
7 import java.net.InetAddress;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.Timer;
17 import java.util.TimerTask;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ConcurrentMap;
20 import java.util.concurrent.ExecutorService;
21 import java.util.concurrent.Executors;
22 import java.util.concurrent.TimeUnit;
23 import java.util.logging.Level;
24 import java.util.logging.Logger;
25 
26 import javax.jmdns.JmDNS;
27 import javax.jmdns.JmmDNS;
28 import javax.jmdns.NetworkTopologyDiscovery;
29 import javax.jmdns.NetworkTopologyEvent;
30 import javax.jmdns.NetworkTopologyListener;
31 import javax.jmdns.ServiceInfo;
32 import javax.jmdns.ServiceListener;
33 import javax.jmdns.ServiceTypeListener;
34 import javax.jmdns.impl.constants.DNSConstants;
35 
36 /**
37  * This class enable multihomming mDNS. It will open a mDNS per IP address of the machine.
38  *
39  * @author Cédrik Lime, Pierre Frisch
40  */
41 public class JmmDNSImpl implements JmmDNS, NetworkTopologyListener, ServiceInfoImpl.Delegate {
42     private static Logger                            logger = Logger.getLogger(JmmDNSImpl.class.getName());
43 
44     private final Set<NetworkTopologyListener>       _networkListeners;
45 
46     /**
47      * Every JmDNS created.
48      */
49     private final ConcurrentMap<InetAddress, JmDNS>  _knownMDNS;
50 
51     /**
52      * This enable the service info text update.
53      */
54     private final ConcurrentMap<String, ServiceInfo> _services;
55 
56     private final ExecutorService                    _ListenerExecutor;
57 
58     private final ExecutorService                    _jmDNSExecutor;
59 
60     private final Timer                              _timer;
61 
62     /**
63      *
64      */
JmmDNSImpl()65     public JmmDNSImpl() {
66         super();
67         _networkListeners = Collections.synchronizedSet(new HashSet<NetworkTopologyListener>());
68         _knownMDNS = new ConcurrentHashMap<InetAddress, JmDNS>();
69         _services = new ConcurrentHashMap<String, ServiceInfo>(20);
70         _ListenerExecutor = Executors.newSingleThreadExecutor();
71         _jmDNSExecutor = Executors.newCachedThreadPool();
72         _timer = new Timer("Multihommed mDNS.Timer", true);
73         (new NetworkChecker(this, NetworkTopologyDiscovery.Factory.getInstance())).start(_timer);
74     }
75 
76     /*
77      * (non-Javadoc)
78      * @see java.io.Closeable#close()
79      */
80     @Override
close()81     public void close() throws IOException {
82         if (logger.isLoggable(Level.FINER)) {
83             logger.finer("Cancelling JmmDNS: " + this);
84         }
85         _timer.cancel();
86         _ListenerExecutor.shutdown();
87         // We need to cancel all the DNS
88         ExecutorService executor = Executors.newCachedThreadPool();
89         for (final JmDNS mDNS : _knownMDNS.values()) {
90             executor.submit(new Runnable() {
91                 /**
92                  * {@inheritDoc}
93                  */
94                 @Override
95                 public void run() {
96                     try {
97                         mDNS.close();
98                     } catch (IOException exception) {
99                         // JmDNS never throws this is only because of the closeable interface
100                     }
101                 }
102             });
103         }
104         executor.shutdown();
105         try {
106             executor.awaitTermination(DNSConstants.CLOSE_TIMEOUT, TimeUnit.MILLISECONDS);
107         } catch (InterruptedException exception) {
108             logger.log(Level.WARNING, "Exception ", exception);
109         }
110         _knownMDNS.clear();
111     }
112 
113     /*
114      * (non-Javadoc)
115      * @see javax.jmdns.JmmDNS#getNames()
116      */
117     @Override
getNames()118     public String[] getNames() {
119         Set<String> result = new HashSet<String>();
120         for (JmDNS mDNS : _knownMDNS.values()) {
121             result.add(mDNS.getName());
122         }
123         return result.toArray(new String[result.size()]);
124     }
125 
126     /*
127      * (non-Javadoc)
128      * @see javax.jmdns.JmmDNS#getHostNames()
129      */
130     @Override
getHostNames()131     public String[] getHostNames() {
132         Set<String> result = new HashSet<String>();
133         for (JmDNS mDNS : _knownMDNS.values()) {
134             result.add(mDNS.getHostName());
135         }
136         return result.toArray(new String[result.size()]);
137     }
138 
139     /*
140      * (non-Javadoc)
141      * @see javax.jmdns.JmmDNS#getInetAddresses()
142      */
143     @Override
getInetAddresses()144     public InetAddress[] getInetAddresses() throws IOException {
145         Set<InetAddress> result = new HashSet<InetAddress>();
146         for (JmDNS mDNS : _knownMDNS.values()) {
147             result.add(mDNS.getInetAddress());
148         }
149         return result.toArray(new InetAddress[result.size()]);
150     }
151 
152     /*
153      * (non-Javadoc)
154      * @see javax.jmdns.JmmDNS#getInterfaces()
155      */
156     @Override
157     @Deprecated
getInterfaces()158     public InetAddress[] getInterfaces() throws IOException {
159         Set<InetAddress> result = new HashSet<InetAddress>();
160         for (JmDNS mDNS : _knownMDNS.values()) {
161             result.add(mDNS.getInterface());
162         }
163         return result.toArray(new InetAddress[result.size()]);
164     }
165 
166     /*
167      * (non-Javadoc)
168      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String)
169      */
170     @Override
getServiceInfos(String type, String name)171     public ServiceInfo[] getServiceInfos(String type, String name) {
172         return this.getServiceInfos(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
173     }
174 
175     /*
176      * (non-Javadoc)
177      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, long)
178      */
179     @Override
getServiceInfos(String type, String name, long timeout)180     public ServiceInfo[] getServiceInfos(String type, String name, long timeout) {
181         return this.getServiceInfos(type, name, false, timeout);
182     }
183 
184     /*
185      * (non-Javadoc)
186      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean)
187      */
188     @Override
getServiceInfos(String type, String name, boolean persistent)189     public ServiceInfo[] getServiceInfos(String type, String name, boolean persistent) {
190         return this.getServiceInfos(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
191     }
192 
193     /*
194      * (non-Javadoc)
195      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean, long)
196      */
197     @Override
getServiceInfos(final String type, final String name, final boolean persistent, final long timeout)198     public ServiceInfo[] getServiceInfos(final String type, final String name, final boolean persistent, final long timeout) {
199         // We need to run this in parallel to respect the timeout.
200         final Set<ServiceInfo> result = Collections.synchronizedSet(new HashSet<ServiceInfo>(_knownMDNS.size()));
201         ExecutorService executor = Executors.newCachedThreadPool();
202         for (final JmDNS mDNS : _knownMDNS.values()) {
203             executor.submit(new Runnable() {
204                 /**
205                  * {@inheritDoc}
206                  */
207                 @Override
208                 public void run() {
209                     result.add(mDNS.getServiceInfo(type, name, persistent, timeout));
210                 }
211             });
212         }
213         executor.shutdown();
214         try {
215             executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
216         } catch (InterruptedException exception) {
217             logger.log(Level.WARNING, "Exception ", exception);
218         }
219         return result.toArray(new ServiceInfo[result.size()]);
220     }
221 
222     /*
223      * (non-Javadoc)
224      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String)
225      */
226     @Override
requestServiceInfo(String type, String name)227     public void requestServiceInfo(String type, String name) {
228         this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
229     }
230 
231     /*
232      * (non-Javadoc)
233      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean)
234      */
235     @Override
requestServiceInfo(String type, String name, boolean persistent)236     public void requestServiceInfo(String type, String name, boolean persistent) {
237         this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
238     }
239 
240     /*
241      * (non-Javadoc)
242      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, long)
243      */
244     @Override
requestServiceInfo(String type, String name, long timeout)245     public void requestServiceInfo(String type, String name, long timeout) {
246         this.requestServiceInfo(type, name, false, timeout);
247     }
248 
249     /*
250      * (non-Javadoc)
251      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean, long)
252      */
253     @Override
requestServiceInfo(final String type, final String name, final boolean persistent, final long timeout)254     public void requestServiceInfo(final String type, final String name, final boolean persistent, final long timeout) {
255         // We need to run this in parallel to respect the timeout.
256         for (final JmDNS mDNS : _knownMDNS.values()) {
257             _jmDNSExecutor.submit(new Runnable() {
258                 /**
259                  * {@inheritDoc}
260                  */
261                 @Override
262                 public void run() {
263                     mDNS.requestServiceInfo(type, name, persistent, timeout);
264                 }
265             });
266         }
267     }
268 
269     /*
270      * (non-Javadoc)
271      * @see javax.jmdns.JmmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener)
272      */
273     @Override
addServiceTypeListener(ServiceTypeListener listener)274     public void addServiceTypeListener(ServiceTypeListener listener) throws IOException {
275         for (JmDNS mDNS : _knownMDNS.values()) {
276             mDNS.addServiceTypeListener(listener);
277         }
278     }
279 
280     /*
281      * (non-Javadoc)
282      * @see javax.jmdns.JmmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener)
283      */
284     @Override
removeServiceTypeListener(ServiceTypeListener listener)285     public void removeServiceTypeListener(ServiceTypeListener listener) {
286         for (JmDNS mDNS : _knownMDNS.values()) {
287             mDNS.removeServiceTypeListener(listener);
288         }
289     }
290 
291     /*
292      * (non-Javadoc)
293      * @see javax.jmdns.JmmDNS#addServiceListener(java.lang.String, javax.jmdns.ServiceListener)
294      */
295     @Override
addServiceListener(String type, ServiceListener listener)296     public void addServiceListener(String type, ServiceListener listener) {
297         for (JmDNS mDNS : _knownMDNS.values()) {
298             mDNS.addServiceListener(type, listener);
299         }
300     }
301 
302     /*
303      * (non-Javadoc)
304      * @see javax.jmdns.JmmDNS#removeServiceListener(java.lang.String, javax.jmdns.ServiceListener)
305      */
306     @Override
removeServiceListener(String type, ServiceListener listener)307     public void removeServiceListener(String type, ServiceListener listener) {
308         for (JmDNS mDNS : _knownMDNS.values()) {
309             mDNS.removeServiceListener(type, listener);
310         }
311     }
312 
313     /*
314      * (non-Javadoc)
315      * @see javax.jmdns.impl.ServiceInfoImpl.Delegate#textValueUpdated(javax.jmdns.ServiceInfo, byte[])
316      */
317     @Override
textValueUpdated(ServiceInfo target, byte[] value)318     public void textValueUpdated(ServiceInfo target, byte[] value) {
319         synchronized (_services) {
320             for (JmDNS mDNS : _knownMDNS.values()) {
321                 ServiceInfo info = ((JmDNSImpl) mDNS).getServices().get(target.getQualifiedName());
322                 if (info != null) {
323                     info.setText(value);
324                 } else {
325                     logger.warning("We have a mDNS that does not know about the service info being updated.");
326                 }
327             }
328         }
329     }
330 
331     /*
332      * (non-Javadoc)
333      * @see javax.jmdns.JmmDNS#registerService(javax.jmdns.ServiceInfo)
334      */
335     @Override
registerService(ServiceInfo info)336     public void registerService(ServiceInfo info) throws IOException {
337         // This is really complex. We need to clone the service info for each DNS but then we loose the ability to update it.
338         synchronized (_services) {
339             for (JmDNS mDNS : _knownMDNS.values()) {
340                 mDNS.registerService(info.clone());
341             }
342             ((ServiceInfoImpl) info).setDelegate(this);
343             _services.put(info.getQualifiedName(), info);
344         }
345     }
346 
347     /*
348      * (non-Javadoc)
349      * @see javax.jmdns.JmmDNS#unregisterService(javax.jmdns.ServiceInfo)
350      */
351     @Override
unregisterService(ServiceInfo info)352     public void unregisterService(ServiceInfo info) {
353         synchronized (_services) {
354             for (JmDNS mDNS : _knownMDNS.values()) {
355                 mDNS.unregisterService(info);
356             }
357             ((ServiceInfoImpl) info).setDelegate(null);
358             _services.remove(info.getQualifiedName());
359         }
360     }
361 
362     /*
363      * (non-Javadoc)
364      * @see javax.jmdns.JmmDNS#unregisterAllServices()
365      */
366     @Override
unregisterAllServices()367     public void unregisterAllServices() {
368         synchronized (_services) {
369             for (JmDNS mDNS : _knownMDNS.values()) {
370                 mDNS.unregisterAllServices();
371             }
372             _services.clear();
373         }
374     }
375 
376     /*
377      * (non-Javadoc)
378      * @see javax.jmdns.JmmDNS#registerServiceType(java.lang.String)
379      */
380     @Override
registerServiceType(String type)381     public void registerServiceType(String type) {
382         for (JmDNS mDNS : _knownMDNS.values()) {
383             mDNS.registerServiceType(type);
384         }
385     }
386 
387     /*
388      * (non-Javadoc)
389      * @see javax.jmdns.JmmDNS#list(java.lang.String)
390      */
391     @Override
list(String type)392     public ServiceInfo[] list(String type) {
393         return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT);
394     }
395 
396     /*
397      * (non-Javadoc)
398      * @see javax.jmdns.JmmDNS#list(java.lang.String, long)
399      */
400     @Override
list(final String type, final long timeout)401     public ServiceInfo[] list(final String type, final long timeout) {
402         // We need to run this in parallel to respect the timeout.
403         final Set<ServiceInfo> result = Collections.synchronizedSet(new HashSet<ServiceInfo>(_knownMDNS.size() * 5));
404         ExecutorService executor = Executors.newCachedThreadPool();
405         for (final JmDNS mDNS : _knownMDNS.values()) {
406             executor.submit(new Runnable() {
407                 /**
408                  * {@inheritDoc}
409                  */
410                 @Override
411                 public void run() {
412                     result.addAll(Arrays.asList(mDNS.list(type, timeout)));
413                 }
414             });
415         }
416         executor.shutdown();
417         try {
418             executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
419         } catch (InterruptedException exception) {
420             logger.log(Level.WARNING, "Exception ", exception);
421         }
422         return result.toArray(new ServiceInfo[result.size()]);
423     }
424 
425     /*
426      * (non-Javadoc)
427      * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String)
428      */
429     @Override
listBySubtype(String type)430     public Map<String, ServiceInfo[]> listBySubtype(String type) {
431         return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT);
432     }
433 
434     /*
435      * (non-Javadoc)
436      * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String, long)
437      */
438     @Override
listBySubtype(final String type, final long timeout)439     public Map<String, ServiceInfo[]> listBySubtype(final String type, final long timeout) {
440         Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5);
441         for (ServiceInfo info : this.list(type, timeout)) {
442             String subtype = info.getSubtype();
443             if (!map.containsKey(subtype)) {
444                 map.put(subtype, new ArrayList<ServiceInfo>(10));
445             }
446             map.get(subtype).add(info);
447         }
448 
449         Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size());
450         for (String subtype : map.keySet()) {
451             List<ServiceInfo> infoForSubType = map.get(subtype);
452             result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()]));
453         }
454 
455         return result;
456     }
457 
458     /*
459      * (non-Javadoc)
460      * @see javax.jmdns.JmmDNS#addNetworkTopologyListener(javax.jmdns.NetworkTopologyListener)
461      */
462     @Override
addNetworkTopologyListener(NetworkTopologyListener listener)463     public void addNetworkTopologyListener(NetworkTopologyListener listener) {
464         _networkListeners.add(listener);
465     }
466 
467     /*
468      * (non-Javadoc)
469      * @see javax.jmdns.JmmDNS#removeNetworkTopologyListener(javax.jmdns.NetworkTopologyListener)
470      */
471     @Override
removeNetworkTopologyListener(NetworkTopologyListener listener)472     public void removeNetworkTopologyListener(NetworkTopologyListener listener) {
473         _networkListeners.remove(listener);
474     }
475 
476     /*
477      * (non-Javadoc)
478      * @see javax.jmdns.JmmDNS#networkListeners()
479      */
480     @Override
networkListeners()481     public NetworkTopologyListener[] networkListeners() {
482         return _networkListeners.toArray(new NetworkTopologyListener[_networkListeners.size()]);
483     }
484 
485     /*
486      * (non-Javadoc)
487      * @see javax.jmdns.NetworkTopologyListener#inetAddressAdded(javax.jmdns.NetworkTopologyEvent)
488      */
489     @Override
inetAddressAdded(NetworkTopologyEvent event)490     public void inetAddressAdded(NetworkTopologyEvent event) {
491         InetAddress address = event.getInetAddress();
492         try {
493             synchronized (this) {
494                 if (!_knownMDNS.containsKey(address)) {
495                     _knownMDNS.put(address, JmDNS.create(address));
496                     final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(_knownMDNS.get(address), address);
497                     for (final NetworkTopologyListener listener : this.networkListeners()) {
498                         _ListenerExecutor.submit(new Runnable() {
499                             /**
500                              * {@inheritDoc}
501                              */
502                             @Override
503                             public void run() {
504                                 listener.inetAddressAdded(jmdnsEvent);
505                             }
506                         });
507                     }
508                 }
509             }
510         } catch (Exception e) {
511             logger.warning("Unexpected unhandled exception: " + e);
512         }
513     }
514 
515     /*
516      * (non-Javadoc)
517      * @see javax.jmdns.NetworkTopologyListener#inetAddressRemoved(javax.jmdns.NetworkTopologyEvent)
518      */
519     @Override
inetAddressRemoved(NetworkTopologyEvent event)520     public void inetAddressRemoved(NetworkTopologyEvent event) {
521         InetAddress address = event.getInetAddress();
522         try {
523             synchronized (this) {
524                 if (_knownMDNS.containsKey(address)) {
525                     JmDNS mDNS = _knownMDNS.remove(address);
526                     mDNS.close();
527                     final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(mDNS, address);
528                     for (final NetworkTopologyListener listener : this.networkListeners()) {
529                         _ListenerExecutor.submit(new Runnable() {
530                             /**
531                              * {@inheritDoc}
532                              */
533                             @Override
534                             public void run() {
535                                 listener.inetAddressRemoved(jmdnsEvent);
536                             }
537                         });
538                     }
539                 }
540             }
541         } catch (Exception e) {
542             logger.warning("Unexpected unhandled exception: " + e);
543         }
544     }
545 
546     /**
547      * Checks the network state.<br/>
548      * If the network change, this class will reconfigure the list of DNS do adapt to the new configuration.
549      */
550     static class NetworkChecker extends TimerTask {
551         private static Logger                  logger1 = Logger.getLogger(NetworkChecker.class.getName());
552 
553         private final NetworkTopologyListener  _mmDNS;
554 
555         private final NetworkTopologyDiscovery _topology;
556 
557         private Set<InetAddress>               _knownAddresses;
558 
NetworkChecker(NetworkTopologyListener mmDNS, NetworkTopologyDiscovery topology)559         public NetworkChecker(NetworkTopologyListener mmDNS, NetworkTopologyDiscovery topology) {
560             super();
561             this._mmDNS = mmDNS;
562             this._topology = topology;
563             _knownAddresses = Collections.synchronizedSet(new HashSet<InetAddress>());
564         }
565 
start(Timer timer)566         public void start(Timer timer) {
567             timer.schedule(this, 0, DNSConstants.NETWORK_CHECK_INTERVAL);
568         }
569 
570         /**
571          * {@inheritDoc}
572          */
573         @Override
run()574         public void run() {
575             try {
576                 InetAddress[] curentAddresses = _topology.getInetAddresses();
577                 Set<InetAddress> current = new HashSet<InetAddress>(curentAddresses.length);
578                 for (InetAddress address : curentAddresses) {
579                     current.add(address);
580                     if (!_knownAddresses.contains(address)) {
581                         final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address);
582                         _mmDNS.inetAddressAdded(event);
583                     }
584                 }
585                 for (InetAddress address : _knownAddresses) {
586                     if (!current.contains(address)) {
587                         final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address);
588                         _mmDNS.inetAddressRemoved(event);
589                     }
590                 }
591                 _knownAddresses = current;
592             } catch (Exception e) {
593                 logger1.warning("Unexpected unhandled exception: " + e);
594             }
595         }
596 
597     }
598 
599 }
600