View Javadoc

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       */
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
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
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
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#getInterfaces()
142      */
143     @Override
144     public InetAddress[] getInterfaces() throws IOException {
145         Set<InetAddress> result = new HashSet<InetAddress>();
146         for (JmDNS mDNS : _knownMDNS.values()) {
147             result.add(mDNS.getInterface());
148         }
149         return result.toArray(new InetAddress[result.size()]);
150     }
151 
152     /*
153      * (non-Javadoc)
154      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String)
155      */
156     @Override
157     public ServiceInfo[] getServiceInfos(String type, String name) {
158         return this.getServiceInfos(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
159     }
160 
161     /*
162      * (non-Javadoc)
163      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, long)
164      */
165     @Override
166     public ServiceInfo[] getServiceInfos(String type, String name, long timeout) {
167         return this.getServiceInfos(type, name, false, timeout);
168     }
169 
170     /*
171      * (non-Javadoc)
172      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean)
173      */
174     @Override
175     public ServiceInfo[] getServiceInfos(String type, String name, boolean persistent) {
176         return this.getServiceInfos(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
177     }
178 
179     /*
180      * (non-Javadoc)
181      * @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean, long)
182      */
183     @Override
184     public ServiceInfo[] getServiceInfos(final String type, final String name, final boolean persistent, final long timeout) {
185         // We need to run this in parallel to respect the timeout.
186         final Set<ServiceInfo> result = Collections.synchronizedSet(new HashSet<ServiceInfo>(_knownMDNS.size()));
187         ExecutorService executor = Executors.newCachedThreadPool();
188         for (final JmDNS mDNS : _knownMDNS.values()) {
189             executor.submit(new Runnable() {
190                 /**
191                  * {@inheritDoc}
192                  */
193                 @Override
194                 public void run() {
195                     result.add(mDNS.getServiceInfo(type, name, persistent, timeout));
196                 }
197             });
198         }
199         executor.shutdown();
200         try {
201             executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
202         } catch (InterruptedException exception) {
203             logger.log(Level.WARNING, "Exception ", exception);
204         }
205         return result.toArray(new ServiceInfo[result.size()]);
206     }
207 
208     /*
209      * (non-Javadoc)
210      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String)
211      */
212     @Override
213     public void requestServiceInfo(String type, String name) {
214         this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
215     }
216 
217     /*
218      * (non-Javadoc)
219      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean)
220      */
221     @Override
222     public void requestServiceInfo(String type, String name, boolean persistent) {
223         this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
224     }
225 
226     /*
227      * (non-Javadoc)
228      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, long)
229      */
230     @Override
231     public void requestServiceInfo(String type, String name, long timeout) {
232         this.requestServiceInfo(type, name, false, timeout);
233     }
234 
235     /*
236      * (non-Javadoc)
237      * @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean, long)
238      */
239     @Override
240     public void requestServiceInfo(final String type, final String name, final boolean persistent, final long timeout) {
241         // We need to run this in parallel to respect the timeout.
242         for (final JmDNS mDNS : _knownMDNS.values()) {
243             _jmDNSExecutor.submit(new Runnable() {
244                 /**
245                  * {@inheritDoc}
246                  */
247                 @Override
248                 public void run() {
249                     mDNS.requestServiceInfo(type, name, persistent, timeout);
250                 }
251             });
252         }
253     }
254 
255     /*
256      * (non-Javadoc)
257      * @see javax.jmdns.JmmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener)
258      */
259     @Override
260     public void addServiceTypeListener(ServiceTypeListener listener) throws IOException {
261         for (JmDNS mDNS : _knownMDNS.values()) {
262             mDNS.addServiceTypeListener(listener);
263         }
264     }
265 
266     /*
267      * (non-Javadoc)
268      * @see javax.jmdns.JmmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener)
269      */
270     @Override
271     public void removeServiceTypeListener(ServiceTypeListener listener) {
272         for (JmDNS mDNS : _knownMDNS.values()) {
273             mDNS.removeServiceTypeListener(listener);
274         }
275     }
276 
277     /*
278      * (non-Javadoc)
279      * @see javax.jmdns.JmmDNS#addServiceListener(java.lang.String, javax.jmdns.ServiceListener)
280      */
281     @Override
282     public void addServiceListener(String type, ServiceListener listener) {
283         for (JmDNS mDNS : _knownMDNS.values()) {
284             mDNS.addServiceListener(type, listener);
285         }
286     }
287 
288     /*
289      * (non-Javadoc)
290      * @see javax.jmdns.JmmDNS#removeServiceListener(java.lang.String, javax.jmdns.ServiceListener)
291      */
292     @Override
293     public void removeServiceListener(String type, ServiceListener listener) {
294         for (JmDNS mDNS : _knownMDNS.values()) {
295             mDNS.removeServiceListener(type, listener);
296         }
297     }
298 
299     /*
300      * (non-Javadoc)
301      * @see javax.jmdns.impl.ServiceInfoImpl.Delegate#textValueUpdated(javax.jmdns.ServiceInfo, byte[])
302      */
303     @Override
304     public void textValueUpdated(ServiceInfo target, byte[] value) {
305         synchronized (_services) {
306             for (JmDNS mDNS : _knownMDNS.values()) {
307                 ServiceInfo info = ((JmDNSImpl) mDNS).getServices().get(target.getQualifiedName());
308                 if (info != null) {
309                     info.setText(value);
310                 } else {
311                     logger.warning("We have a mDNS that does not know about the service info being updated.");
312                 }
313             }
314         }
315     }
316 
317     /*
318      * (non-Javadoc)
319      * @see javax.jmdns.JmmDNS#registerService(javax.jmdns.ServiceInfo)
320      */
321     @Override
322     public void registerService(ServiceInfo info) throws IOException {
323         // This is really complex. We need to clone the service info for each DNS but then we loose the ability to update it.
324         synchronized (_services) {
325             for (JmDNS mDNS : _knownMDNS.values()) {
326                 mDNS.registerService(info.clone());
327             }
328             ((ServiceInfoImpl) info).setDelegate(this);
329             _services.put(info.getQualifiedName(), info);
330         }
331     }
332 
333     /*
334      * (non-Javadoc)
335      * @see javax.jmdns.JmmDNS#unregisterService(javax.jmdns.ServiceInfo)
336      */
337     @Override
338     public void unregisterService(ServiceInfo info) {
339         synchronized (_services) {
340             for (JmDNS mDNS : _knownMDNS.values()) {
341                 mDNS.unregisterService(info);
342             }
343             ((ServiceInfoImpl) info).setDelegate(null);
344             _services.remove(info.getQualifiedName());
345         }
346     }
347 
348     /*
349      * (non-Javadoc)
350      * @see javax.jmdns.JmmDNS#unregisterAllServices()
351      */
352     @Override
353     public void unregisterAllServices() {
354         synchronized (_services) {
355             for (JmDNS mDNS : _knownMDNS.values()) {
356                 mDNS.unregisterAllServices();
357             }
358             _services.clear();
359         }
360     }
361 
362     /*
363      * (non-Javadoc)
364      * @see javax.jmdns.JmmDNS#registerServiceType(java.lang.String)
365      */
366     @Override
367     public void registerServiceType(String type) {
368         for (JmDNS mDNS : _knownMDNS.values()) {
369             mDNS.registerServiceType(type);
370         }
371     }
372 
373     /*
374      * (non-Javadoc)
375      * @see javax.jmdns.JmmDNS#list(java.lang.String)
376      */
377     @Override
378     public ServiceInfo[] list(String type) {
379         return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT);
380     }
381 
382     /*
383      * (non-Javadoc)
384      * @see javax.jmdns.JmmDNS#list(java.lang.String, long)
385      */
386     @Override
387     public ServiceInfo[] list(final String type, final long timeout) {
388         // We need to run this in parallel to respect the timeout.
389         final Set<ServiceInfo> result = Collections.synchronizedSet(new HashSet<ServiceInfo>(_knownMDNS.size() * 5));
390         ExecutorService executor = Executors.newCachedThreadPool();
391         for (final JmDNS mDNS : _knownMDNS.values()) {
392             executor.submit(new Runnable() {
393                 /**
394                  * {@inheritDoc}
395                  */
396                 @Override
397                 public void run() {
398                     result.addAll(Arrays.asList(mDNS.list(type, timeout)));
399                 }
400             });
401         }
402         executor.shutdown();
403         try {
404             executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
405         } catch (InterruptedException exception) {
406             logger.log(Level.WARNING, "Exception ", exception);
407         }
408         return result.toArray(new ServiceInfo[result.size()]);
409     }
410 
411     /*
412      * (non-Javadoc)
413      * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String)
414      */
415     @Override
416     public Map<String, ServiceInfo[]> listBySubtype(String type) {
417         return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT);
418     }
419 
420     /*
421      * (non-Javadoc)
422      * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String, long)
423      */
424     @Override
425     public Map<String, ServiceInfo[]> listBySubtype(final String type, final long timeout) {
426         Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5);
427         for (ServiceInfo info : this.list(type, timeout)) {
428             String subtype = info.getSubtype();
429             if (!map.containsKey(subtype)) {
430                 map.put(subtype, new ArrayList<ServiceInfo>(10));
431             }
432             map.get(subtype).add(info);
433         }
434 
435         Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size());
436         for (String subtype : map.keySet()) {
437             List<ServiceInfo> infoForSubType = map.get(subtype);
438             result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()]));
439         }
440 
441         return result;
442     }
443 
444     /*
445      * (non-Javadoc)
446      * @see javax.jmdns.JmmDNS#addNetworkTopologyListener(javax.jmdns.NetworkTopologyListener)
447      */
448     @Override
449     public void addNetworkTopologyListener(NetworkTopologyListener listener) {
450         _networkListeners.add(listener);
451     }
452 
453     /*
454      * (non-Javadoc)
455      * @see javax.jmdns.JmmDNS#removeNetworkTopologyListener(javax.jmdns.NetworkTopologyListener)
456      */
457     @Override
458     public void removeNetworkTopologyListener(NetworkTopologyListener listener) {
459         _networkListeners.remove(listener);
460     }
461 
462     /*
463      * (non-Javadoc)
464      * @see javax.jmdns.JmmDNS#networkListeners()
465      */
466     @Override
467     public NetworkTopologyListener[] networkListeners() {
468         return _networkListeners.toArray(new NetworkTopologyListener[_networkListeners.size()]);
469     }
470 
471     /*
472      * (non-Javadoc)
473      * @see javax.jmdns.NetworkTopologyListener#inetAddressAdded(javax.jmdns.NetworkTopologyEvent)
474      */
475     @Override
476     public void inetAddressAdded(NetworkTopologyEvent event) {
477         InetAddress address = event.getInetAddress();
478         try {
479             synchronized (this) {
480                 if (!_knownMDNS.containsKey(address)) {
481                     _knownMDNS.put(address, JmDNS.create(address));
482                     final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(_knownMDNS.get(address), address);
483                     for (final NetworkTopologyListener listener : this.networkListeners()) {
484                         _ListenerExecutor.submit(new Runnable() {
485                             /**
486                              * {@inheritDoc}
487                              */
488                             @Override
489                             public void run() {
490                                 listener.inetAddressAdded(jmdnsEvent);
491                             }
492                         });
493                     }
494                 }
495             }
496         } catch (Exception e) {
497             logger.warning("Unexpected unhandled exception: " + e);
498         }
499     }
500 
501     /*
502      * (non-Javadoc)
503      * @see javax.jmdns.NetworkTopologyListener#inetAddressRemoved(javax.jmdns.NetworkTopologyEvent)
504      */
505     @Override
506     public void inetAddressRemoved(NetworkTopologyEvent event) {
507         InetAddress address = event.getInetAddress();
508         try {
509             synchronized (this) {
510                 if (_knownMDNS.containsKey(address)) {
511                     JmDNS mDNS = _knownMDNS.remove(address);
512                     mDNS.close();
513                     final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(mDNS, address);
514                     for (final NetworkTopologyListener listener : this.networkListeners()) {
515                         _ListenerExecutor.submit(new Runnable() {
516                             /**
517                              * {@inheritDoc}
518                              */
519                             @Override
520                             public void run() {
521                                 listener.inetAddressRemoved(jmdnsEvent);
522                             }
523                         });
524                     }
525                 }
526             }
527         } catch (Exception e) {
528             logger.warning("Unexpected unhandled exception: " + e);
529         }
530     }
531 
532     /**
533      * Checks the network state.<br/>
534      * If the network change, this class will reconfigure the list of DNS do adapt to the new configuration.
535      */
536     static class NetworkChecker extends TimerTask {
537         private static Logger                  logger1 = Logger.getLogger(NetworkChecker.class.getName());
538 
539         private final NetworkTopologyListener  _mmDNS;
540 
541         private final NetworkTopologyDiscovery _topology;
542 
543         private Set<InetAddress>               _knownAddresses;
544 
545         public NetworkChecker(NetworkTopologyListener mmDNS, NetworkTopologyDiscovery topology) {
546             super();
547             this._mmDNS = mmDNS;
548             this._topology = topology;
549             _knownAddresses = Collections.synchronizedSet(new HashSet<InetAddress>());
550         }
551 
552         public void start(Timer timer) {
553             timer.schedule(this, 0, DNSConstants.NETWORK_CHECK_INTERVAL);
554         }
555 
556         /**
557          * {@inheritDoc}
558          */
559         @Override
560         public void run() {
561             try {
562                 InetAddress[] curentAddresses = _topology.getInetAddresses();
563                 Set<InetAddress> current = new HashSet<InetAddress>(curentAddresses.length);
564                 for (InetAddress address : curentAddresses) {
565                     current.add(address);
566                     if (!_knownAddresses.contains(address)) {
567                         final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address);
568                         _mmDNS.inetAddressAdded(event);
569                     }
570                 }
571                 for (InetAddress address : _knownAddresses) {
572                     if (!current.contains(address)) {
573                         final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address);
574                         _mmDNS.inetAddressRemoved(event);
575                     }
576                 }
577                 _knownAddresses = current;
578             } catch (Exception e) {
579                 logger1.warning("Unexpected unhandled exception: " + e);
580             }
581         }
582 
583     }
584 
585 }