1/** 2 * 3 */ 4package javax.jmdns.impl; 5 6import java.io.IOException; 7import java.net.InetAddress; 8import java.util.ArrayList; 9import java.util.Arrays; 10import java.util.Collections; 11import java.util.HashMap; 12import java.util.HashSet; 13import java.util.List; 14import java.util.Map; 15import java.util.Set; 16import java.util.Timer; 17import java.util.TimerTask; 18import java.util.concurrent.ConcurrentHashMap; 19import java.util.concurrent.ConcurrentMap; 20import java.util.concurrent.ExecutorService; 21import java.util.concurrent.Executors; 22import java.util.concurrent.TimeUnit; 23import java.util.logging.Level; 24import java.util.logging.Logger; 25 26import javax.jmdns.JmDNS; 27import javax.jmdns.JmmDNS; 28import javax.jmdns.NetworkTopologyDiscovery; 29import javax.jmdns.NetworkTopologyEvent; 30import javax.jmdns.NetworkTopologyListener; 31import javax.jmdns.ServiceInfo; 32import javax.jmdns.ServiceListener; 33import javax.jmdns.ServiceTypeListener; 34import 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 */ 41public 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#getInetAddresses() 142 */ 143 @Override 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 566 public void start(Timer timer) { 567 timer.schedule(this, 0, DNSConstants.NETWORK_CHECK_INTERVAL); 568 } 569 570 /** 571 * {@inheritDoc} 572 */ 573 @Override 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