1 // /Copyright 2003-2005 Arthur van Hoff, Rick Blair 2 // Licensed under Apache License version 2.0 3 // Original license LGPL 4 5 package javax.jmdns.impl; 6 7 import java.io.IOException; 8 import java.net.DatagramPacket; 9 import java.net.Inet4Address; 10 import java.net.Inet6Address; 11 import java.net.InetAddress; 12 import java.net.MulticastSocket; 13 import java.net.SocketException; 14 import java.util.AbstractMap; 15 import java.util.ArrayList; 16 import java.util.Collection; 17 import java.util.Collections; 18 import java.util.HashMap; 19 import java.util.HashSet; 20 import java.util.Iterator; 21 import java.util.LinkedList; 22 import java.util.List; 23 import java.util.Map; 24 import java.util.Properties; 25 import java.util.Random; 26 import java.util.Set; 27 import java.util.concurrent.ConcurrentHashMap; 28 import java.util.concurrent.ConcurrentMap; 29 import java.util.concurrent.ExecutorService; 30 import java.util.concurrent.Executors; 31 import java.util.concurrent.locks.ReentrantLock; 32 import java.util.logging.Level; 33 import java.util.logging.Logger; 34 35 import javax.jmdns.JmDNS; 36 import javax.jmdns.ServiceEvent; 37 import javax.jmdns.ServiceInfo; 38 import javax.jmdns.ServiceInfo.Fields; 39 import javax.jmdns.ServiceListener; 40 import javax.jmdns.ServiceTypeListener; 41 import javax.jmdns.impl.ListenerStatus.ServiceListenerStatus; 42 import javax.jmdns.impl.ListenerStatus.ServiceTypeListenerStatus; 43 import javax.jmdns.impl.constants.DNSConstants; 44 import javax.jmdns.impl.constants.DNSRecordClass; 45 import javax.jmdns.impl.constants.DNSRecordType; 46 import javax.jmdns.impl.constants.DNSState; 47 import javax.jmdns.impl.tasks.DNSTask; 48 49 // REMIND: multiple IP addresses 50 51 /** 52 * mDNS implementation in Java. 53 * 54 * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Scott Lewis 55 */ 56 public class JmDNSImpl extends JmDNS implements DNSStatefulObject, DNSTaskStarter { 57 private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName()); 58 59 public enum Operation { 60 Remove, Update, Add, RegisterServiceType, Noop 61 } 62 63 /** 64 * This is the multicast group, we are listening to for multicast DNS messages. 65 */ 66 private volatile InetAddress _group; 67 /** 68 * This is our multicast socket. 69 */ 70 private volatile MulticastSocket _socket; 71 72 /** 73 * Holds instances of JmDNS.DNSListener. Must by a synchronized collection, because it is updated from concurrent threads. 74 */ 75 private final List<DNSListener> _listeners; 76 77 /** 78 * Holds instances of ServiceListener's. Keys are Strings holding a fully qualified service type. Values are LinkedList's of ServiceListener's. 79 */ 80 private final ConcurrentMap<String, List<ServiceListenerStatus>> _serviceListeners; 81 82 /** 83 * Holds instances of ServiceTypeListener's. 84 */ 85 private final Set<ServiceTypeListenerStatus> _typeListeners; 86 87 /** 88 * Cache for DNSEntry's. 89 */ 90 private final DNSCache _cache; 91 92 /** 93 * This hashtable holds the services that have been registered. Keys are instances of String which hold an all lower-case version of the fully qualified service name. Values are instances of ServiceInfo. 94 */ 95 private final ConcurrentMap<String, ServiceInfo> _services; 96 97 /** 98 * This hashtable holds the service types that have been registered or that have been received in an incoming datagram.<br/> 99 * Keys are instances of String which hold an all lower-case version of the fully qualified service type.<br/> 100 * Values hold the fully qualified service type. 101 */ 102 private final ConcurrentMap<String, ServiceTypeEntry> _serviceTypes; 103 104 private volatile Delegate _delegate; 105 106 /** 107 * This is used to store type entries. The type is stored as a call variable and the map support the subtypes. 108 * <p> 109 * The key is the lowercase version as the value is the case preserved version. 110 * </p> 111 */ 112 public static class ServiceTypeEntry extends AbstractMap<String, String> implements Cloneable { 113 114 private final Set<Map.Entry<String, String>> _entrySet; 115 116 private final String _type; 117 118 private static class SubTypeEntry implements Entry<String, String>, java.io.Serializable, Cloneable { 119 120 private static final long serialVersionUID = 9188503522395855322L; 121 122 private final String _key; 123 private final String _value; 124 125 public SubTypeEntry(String subtype) { 126 super(); 127 _value = (subtype != null ? subtype : ""); 128 _key = _value.toLowerCase(); 129 } 130 131 /** 132 * {@inheritDoc} 133 */ 134 @Override 135 public String getKey() { 136 return _key; 137 } 138 139 /** 140 * {@inheritDoc} 141 */ 142 @Override 143 public String getValue() { 144 return _value; 145 } 146 147 /** 148 * Replaces the value corresponding to this entry with the specified value (optional operation). This implementation simply throws <tt>UnsupportedOperationException</tt>, as this class implements an <i>immutable</i> map entry. 149 * 150 * @param value 151 * new value to be stored in this entry 152 * @return (Does not return) 153 * @exception UnsupportedOperationException 154 * always 155 */ 156 @Override 157 public String setValue(String value) { 158 throw new UnsupportedOperationException(); 159 } 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 public boolean equals(Object entry) { 166 if (!(entry instanceof Map.Entry)) { 167 return false; 168 } 169 return this.getKey().equals(((Map.Entry<?, ?>) entry).getKey()) && this.getValue().equals(((Map.Entry<?, ?>) entry).getValue()); 170 } 171 172 /** 173 * {@inheritDoc} 174 */ 175 @Override 176 public int hashCode() { 177 return (_key == null ? 0 : _key.hashCode()) ^ (_value == null ? 0 : _value.hashCode()); 178 } 179 180 /* 181 * (non-Javadoc) 182 * @see java.lang.Object#clone() 183 */ 184 @Override 185 public SubTypeEntry clone() { 186 // Immutable object 187 return this; 188 } 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override 194 public String toString() { 195 return _key + "=" + _value; 196 } 197 198 } 199 200 public ServiceTypeEntry(String type) { 201 super(); 202 this._type = type; 203 this._entrySet = new HashSet<Map.Entry<String, String>>(); 204 } 205 206 /** 207 * The type associated with this entry. 208 * 209 * @return the type 210 */ 211 public String getType() { 212 return _type; 213 } 214 215 /* 216 * (non-Javadoc) 217 * @see java.util.AbstractMap#entrySet() 218 */ 219 @Override 220 public Set<Map.Entry<String, String>> entrySet() { 221 return _entrySet; 222 } 223 224 /** 225 * Returns <code>true</code> if this set contains the specified element. More formally, returns <code>true</code> if and only if this set contains an element <code>e</code> such that 226 * <code>(o==null ? e==null : o.equals(e))</code>. 227 * 228 * @param subtype 229 * element whose presence in this set is to be tested 230 * @return <code>true</code> if this set contains the specified element 231 */ 232 public boolean contains(String subtype) { 233 return subtype != null && this.containsKey(subtype.toLowerCase()); 234 } 235 236 /** 237 * Adds the specified element to this set if it is not already present. More formally, adds the specified element <code>e</code> to this set if this set contains no element <code>e2</code> such that 238 * <code>(e==null ? e2==null : e.equals(e2))</code>. If this set already contains the element, the call leaves the set unchanged and returns <code>false</code>. 239 * 240 * @param subtype 241 * element to be added to this set 242 * @return <code>true</code> if this set did not already contain the specified element 243 */ 244 public boolean add(String subtype) { 245 if (subtype == null || this.contains(subtype)) { 246 return false; 247 } 248 _entrySet.add(new SubTypeEntry(subtype)); 249 return true; 250 } 251 252 /** 253 * Returns an iterator over the elements in this set. The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee). 254 * 255 * @return an iterator over the elements in this set 256 */ 257 public Iterator<String> iterator() { 258 return this.keySet().iterator(); 259 } 260 261 /* 262 * (non-Javadoc) 263 * @see java.util.AbstractMap#clone() 264 */ 265 @Override 266 public ServiceTypeEntry clone() { 267 ServiceTypeEntry entry = new ServiceTypeEntry(this.getType()); 268 for (Map.Entry<String, String> subTypeEntry : this.entrySet()) { 269 entry.add(subTypeEntry.getValue()); 270 } 271 return entry; 272 } 273 274 /* 275 * (non-Javadoc) 276 * @see java.util.AbstractMap#toString() 277 */ 278 @Override 279 public String toString() { 280 final StringBuilder aLog = new StringBuilder(200); 281 if (this.isEmpty()) { 282 aLog.append("empty"); 283 } else { 284 for (String value : this.values()) { 285 aLog.append(value); 286 aLog.append(", "); 287 } 288 aLog.setLength(aLog.length() - 2); 289 } 290 return aLog.toString(); 291 } 292 293 } 294 295 /** 296 * This is the shutdown hook, we registered with the java runtime. 297 */ 298 protected Thread _shutdown; 299 300 /** 301 * Handle on the local host 302 */ 303 private HostInfo _localHost; 304 305 private Thread _incomingListener; 306 307 /** 308 * Throttle count. This is used to count the overall number of probes sent by JmDNS. When the last throttle increment happened . 309 */ 310 private int _throttle; 311 312 /** 313 * Last throttle increment. 314 */ 315 private long _lastThrottleIncrement; 316 317 private final ExecutorService _executor = Executors.newSingleThreadExecutor(); 318 319 // 320 // 2009-09-16 ldeck: adding docbug patch with slight ammendments 321 // 'Fixes two deadlock conditions involving JmDNS.close() - ID: 1473279' 322 // 323 // --------------------------------------------------- 324 /** 325 * The timer that triggers our announcements. We can't use the main timer object, because that could cause a deadlock where Prober waits on JmDNS.this lock held by close(), close() waits for us to finish, and we wait for Prober to give us back 326 * the timer thread so we can announce. (Patch from docbug in 2006-04-19 still wasn't patched .. so I'm doing it!) 327 */ 328 // private final Timer _cancelerTimer; 329 // --------------------------------------------------- 330 331 /** 332 * The source for random values. This is used to introduce random delays in responses. This reduces the potential for collisions on the network. 333 */ 334 private final static Random _random = new Random(); 335 336 /** 337 * This lock is used to coordinate processing of incoming and outgoing messages. This is needed, because the Rendezvous Conformance Test does not forgive race conditions. 338 */ 339 private final ReentrantLock _ioLock = new ReentrantLock(); 340 341 /** 342 * If an incoming package which needs an answer is truncated, we store it here. We add more incoming DNSRecords to it, until the JmDNS.Responder timer picks it up.<br/> 343 * FIXME [PJYF June 8 2010]: This does not work well with multiple planned answers for packages that came in from different clients. 344 */ 345 private DNSIncoming _plannedAnswer; 346 347 // State machine 348 349 /** 350 * This hashtable is used to maintain a list of service types being collected by this JmDNS instance. The key of the hashtable is a service type name, the value is an instance of JmDNS.ServiceCollector. 351 * 352 * @see #list 353 */ 354 private final ConcurrentMap<String, ServiceCollector> _serviceCollectors; 355 356 private final String _name; 357 358 /** 359 * Main method to display API information if run from java -jar 360 * 361 * @param argv 362 * the command line arguments 363 */ 364 public static void main(String[] argv) { 365 String version = null; 366 try { 367 final Properties pomProperties = new Properties(); 368 pomProperties.load(JmDNSImpl.class.getResourceAsStream("/META-INF/maven/javax.jmdns/jmdns/pom.properties")); 369 version = pomProperties.getProperty("version"); 370 } catch (Exception e) { 371 version = "RUNNING.IN.IDE.FULL"; 372 } 373 System.out.println("JmDNS version \"" + version + "\""); 374 System.out.println(" "); 375 376 System.out.println("Running on java version \"" + System.getProperty("java.version") + "\"" + " (build " + System.getProperty("java.runtime.version") + ")" + " from " + System.getProperty("java.vendor")); 377 378 System.out.println("Operating environment \"" + System.getProperty("os.name") + "\"" + " version " + System.getProperty("os.version") + " on " + System.getProperty("os.arch")); 379 380 System.out.println("For more information on JmDNS please visit https://sourceforge.net/projects/jmdns/"); 381 } 382 383 /** 384 * Create an instance of JmDNS and bind it to a specific network interface given its IP-address. 385 * 386 * @param address 387 * IP address to bind to. 388 * @param name 389 * name of the newly created JmDNS 390 * @exception IOException 391 */ 392 public JmDNSImpl(InetAddress address, String name) throws IOException { 393 super(); 394 if (logger.isLoggable(Level.FINER)) { 395 logger.finer("JmDNS instance created"); 396 } 397 _cache = new DNSCache(100); 398 399 _listeners = Collections.synchronizedList(new ArrayList<DNSListener>()); 400 _serviceListeners = new ConcurrentHashMap<String, List<ServiceListenerStatus>>(); 401 _typeListeners = Collections.synchronizedSet(new HashSet<ServiceTypeListenerStatus>()); 402 _serviceCollectors = new ConcurrentHashMap<String, ServiceCollector>(); 403 404 _services = new ConcurrentHashMap<String, ServiceInfo>(20); 405 _serviceTypes = new ConcurrentHashMap<String, ServiceTypeEntry>(20); 406 407 _localHost = HostInfo.newHostInfo(address, this, name); 408 _name = (name != null ? name : _localHost.getName()); 409 410 // _cancelerTimer = new Timer("JmDNS.cancelerTimer"); 411 412 // (ldeck 2.1.1) preventing shutdown blocking thread 413 // ------------------------------------------------- 414 // _shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown"); 415 // Runtime.getRuntime().addShutdownHook(_shutdown); 416 417 // ------------------------------------------------- 418 419 // Bind to multicast socket 420 this.openMulticastSocket(this.getLocalHost()); 421 this.start(this.getServices().values()); 422 423 this.startReaper(); 424 } 425 426 private void start(Collection<? extends ServiceInfo> serviceInfos) { 427 if (_incomingListener == null) { 428 _incomingListener = new SocketListener(this); 429 _incomingListener.start(); 430 } 431 this.startProber(); 432 for (ServiceInfo info : serviceInfos) { 433 try { 434 this.registerService(new ServiceInfoImpl(info)); 435 } catch (final Exception exception) { 436 logger.log(Level.WARNING, "start() Registration exception ", exception); 437 } 438 } 439 } 440 441 private void openMulticastSocket(HostInfo hostInfo) throws IOException { 442 if (_group == null) { 443 if (hostInfo.getInetAddress() instanceof Inet6Address) { 444 _group = InetAddress.getByName(DNSConstants.MDNS_GROUP_IPV6); 445 } else { 446 _group = InetAddress.getByName(DNSConstants.MDNS_GROUP); 447 } 448 } 449 if (_socket != null) { 450 this.closeMulticastSocket(); 451 } 452 _socket = new MulticastSocket(DNSConstants.MDNS_PORT); 453 if ((hostInfo != null) && (hostInfo.getInterface() != null)) { 454 try { 455 _socket.setNetworkInterface(hostInfo.getInterface()); 456 } catch (SocketException e) { 457 if (logger.isLoggable(Level.FINE)) { 458 logger.fine("openMulticastSocket() Set network interface exception: " + e.getMessage()); 459 } 460 } 461 } 462 _socket.setTimeToLive(255); 463 _socket.joinGroup(_group); 464 } 465 466 private void closeMulticastSocket() { 467 // jP: 20010-01-18. See below. We'll need this monitor... 468 // assert (Thread.holdsLock(this)); 469 if (logger.isLoggable(Level.FINER)) { 470 logger.finer("closeMulticastSocket()"); 471 } 472 if (_socket != null) { 473 // close socket 474 try { 475 try { 476 _socket.leaveGroup(_group); 477 } catch (SocketException exception) { 478 // 479 } 480 _socket.close(); 481 // jP: 20010-01-18. It isn't safe to join() on the listener 482 // thread - it attempts to lock the IoLock object, and deadlock 483 // ensues. Per issue #2933183, changed this to wait on the JmDNS 484 // monitor, checking on each notify (or timeout) that the 485 // listener thread has stopped. 486 // 487 while (_incomingListener != null && _incomingListener.isAlive()) { 488 synchronized (this) { 489 try { 490 if (_incomingListener != null && _incomingListener.isAlive()) { 491 // wait time is arbitrary, we're really expecting notification. 492 if (logger.isLoggable(Level.FINER)) { 493 logger.finer("closeMulticastSocket(): waiting for jmDNS monitor"); 494 } 495 this.wait(1000); 496 } 497 } catch (InterruptedException ignored) { 498 // Ignored 499 } 500 } 501 } 502 _incomingListener = null; 503 } catch (final Exception exception) { 504 logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception); 505 } 506 _socket = null; 507 } 508 } 509 510 // State machine 511 /** 512 * {@inheritDoc} 513 */ 514 @Override 515 public boolean advanceState(DNSTask task) { 516 return this._localHost.advanceState(task); 517 } 518 519 /** 520 * {@inheritDoc} 521 */ 522 @Override 523 public boolean revertState() { 524 return this._localHost.revertState(); 525 } 526 527 /** 528 * {@inheritDoc} 529 */ 530 @Override 531 public boolean cancelState() { 532 return this._localHost.cancelState(); 533 } 534 535 /** 536 * {@inheritDoc} 537 */ 538 @Override 539 public boolean closeState() { 540 return this._localHost.closeState(); 541 } 542 543 /** 544 * {@inheritDoc} 545 */ 546 @Override 547 public boolean recoverState() { 548 return this._localHost.recoverState(); 549 } 550 551 /** 552 * {@inheritDoc} 553 */ 554 @Override 555 public JmDNSImpl getDns() { 556 return this; 557 } 558 559 /** 560 * {@inheritDoc} 561 */ 562 @Override 563 public void associateWithTask(DNSTask task, DNSState state) { 564 this._localHost.associateWithTask(task, state); 565 } 566 567 /** 568 * {@inheritDoc} 569 */ 570 @Override 571 public void removeAssociationWithTask(DNSTask task) { 572 this._localHost.removeAssociationWithTask(task); 573 } 574 575 /** 576 * {@inheritDoc} 577 */ 578 @Override 579 public boolean isAssociatedWithTask(DNSTask task, DNSState state) { 580 return this._localHost.isAssociatedWithTask(task, state); 581 } 582 583 /** 584 * {@inheritDoc} 585 */ 586 @Override 587 public boolean isProbing() { 588 return this._localHost.isProbing(); 589 } 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override 595 public boolean isAnnouncing() { 596 return this._localHost.isAnnouncing(); 597 } 598 599 /** 600 * {@inheritDoc} 601 */ 602 @Override 603 public boolean isAnnounced() { 604 return this._localHost.isAnnounced(); 605 } 606 607 /** 608 * {@inheritDoc} 609 */ 610 @Override 611 public boolean isCanceling() { 612 return this._localHost.isCanceling(); 613 } 614 615 /** 616 * {@inheritDoc} 617 */ 618 @Override 619 public boolean isCanceled() { 620 return this._localHost.isCanceled(); 621 } 622 623 /** 624 * {@inheritDoc} 625 */ 626 @Override 627 public boolean isClosing() { 628 return this._localHost.isClosing(); 629 } 630 631 /** 632 * {@inheritDoc} 633 */ 634 @Override 635 public boolean isClosed() { 636 return this._localHost.isClosed(); 637 } 638 639 /** 640 * {@inheritDoc} 641 */ 642 @Override 643 public boolean waitForAnnounced(long timeout) { 644 return this._localHost.waitForAnnounced(timeout); 645 } 646 647 /** 648 * {@inheritDoc} 649 */ 650 @Override 651 public boolean waitForCanceled(long timeout) { 652 return this._localHost.waitForCanceled(timeout); 653 } 654 655 /** 656 * Return the DNSCache associated with the cache variable 657 * 658 * @return DNS cache 659 */ 660 public DNSCache getCache() { 661 return _cache; 662 } 663 664 /** 665 * {@inheritDoc} 666 */ 667 @Override 668 public String getName() { 669 return _name; 670 } 671 672 /** 673 * {@inheritDoc} 674 */ 675 @Override 676 public String getHostName() { 677 return _localHost.getName(); 678 } 679 680 /** 681 * Returns the local host info 682 * 683 * @return local host info 684 */ 685 public HostInfo getLocalHost() { 686 return _localHost; 687 } 688 689 /** 690 * {@inheritDoc} 691 */ 692 @Override 693 public InetAddress getInetAddress() throws IOException { 694 return _localHost.getInetAddress(); 695 } 696 697 /** 698 * {@inheritDoc} 699 */ 700 @Override 701 @Deprecated 702 public InetAddress getInterface() throws IOException { 703 return _socket.getInterface(); 704 } 705 706 /** 707 * {@inheritDoc} 708 */ 709 @Override 710 public ServiceInfo getServiceInfo(String type, String name) { 711 return this.getServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); 712 } 713 714 /** 715 * {@inheritDoc} 716 */ 717 @Override 718 public ServiceInfo getServiceInfo(String type, String name, long timeout) { 719 return this.getServiceInfo(type, name, false, timeout); 720 } 721 722 /** 723 * {@inheritDoc} 724 */ 725 @Override 726 public ServiceInfo getServiceInfo(String type, String name, boolean persistent) { 727 return this.getServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); 728 } 729 730 /** 731 * {@inheritDoc} 732 */ 733 @Override 734 public ServiceInfo getServiceInfo(String type, String name, boolean persistent, long timeout) { 735 final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent); 736 this.waitForInfoData(info, timeout); 737 return (info.hasData() ? info : null); 738 } 739 740 ServiceInfoImpl resolveServiceInfo(String type, String name, String subtype, boolean persistent) { 741 this.cleanCache(); 742 String loType = type.toLowerCase(); 743 this.registerServiceType(type); 744 if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) { 745 this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS); 746 } 747 748 // Check if the answer is in the cache. 749 final ServiceInfoImpl info = this.getServiceInfoFromCache(type, name, subtype, persistent); 750 // We still run the resolver to do the dispatch but if the info is already there it will quit immediately 751 this.startServiceInfoResolver(info); 752 753 return info; 754 } 755 756 ServiceInfoImpl getServiceInfoFromCache(String type, String name, String subtype, boolean persistent) { 757 // Check if the answer is in the cache. 758 ServiceInfoImpl info = new ServiceInfoImpl(type, name, subtype, 0, 0, 0, persistent, (byte[]) null); 759 DNSEntry pointerEntry = this.getCache().getDNSEntry(new DNSRecord.Pointer(type, DNSRecordClass.CLASS_ANY, false, 0, info.getQualifiedName())); 760 if (pointerEntry instanceof DNSRecord) { 761 ServiceInfoImpl cachedInfo = (ServiceInfoImpl) ((DNSRecord) pointerEntry).getServiceInfo(persistent); 762 if (cachedInfo != null) { 763 // To get a complete info record we need to retrieve the service, address and the text bytes. 764 765 Map<Fields, String> map = cachedInfo.getQualifiedNameMap(); 766 byte[] srvBytes = null; 767 String server = ""; 768 DNSEntry serviceEntry = this.getCache().getDNSEntry(info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_ANY); 769 if (serviceEntry instanceof DNSRecord) { 770 ServiceInfo cachedServiceEntryInfo = ((DNSRecord) serviceEntry).getServiceInfo(persistent); 771 if (cachedServiceEntryInfo != null) { 772 cachedInfo = new ServiceInfoImpl(map, cachedServiceEntryInfo.getPort(), cachedServiceEntryInfo.getWeight(), cachedServiceEntryInfo.getPriority(), persistent, (byte[]) null); 773 srvBytes = cachedServiceEntryInfo.getTextBytes(); 774 server = cachedServiceEntryInfo.getServer(); 775 } 776 } 777 DNSEntry addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_ANY); 778 if (addressEntry instanceof DNSRecord) { 779 ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent); 780 if (cachedAddressInfo != null) { 781 for (Inet4Address address : cachedAddressInfo.getInet4Addresses()) { 782 cachedInfo.addAddress(address); 783 } 784 cachedInfo._setText(cachedAddressInfo.getTextBytes()); 785 } 786 } 787 addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_ANY); 788 if (addressEntry instanceof DNSRecord) { 789 ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent); 790 if (cachedAddressInfo != null) { 791 for (Inet6Address address : cachedAddressInfo.getInet6Addresses()) { 792 cachedInfo.addAddress(address); 793 } 794 cachedInfo._setText(cachedAddressInfo.getTextBytes()); 795 } 796 } 797 DNSEntry textEntry = this.getCache().getDNSEntry(cachedInfo.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_ANY); 798 if (textEntry instanceof DNSRecord) { 799 ServiceInfo cachedTextInfo = ((DNSRecord) textEntry).getServiceInfo(persistent); 800 if (cachedTextInfo != null) { 801 cachedInfo._setText(cachedTextInfo.getTextBytes()); 802 } 803 } 804 if (cachedInfo.getTextBytes().length == 0) { 805 cachedInfo._setText(srvBytes); 806 } 807 if (cachedInfo.hasData()) { 808 info = cachedInfo; 809 } 810 } 811 } 812 return info; 813 } 814 815 private void waitForInfoData(ServiceInfo info, long timeout) { 816 synchronized (info) { 817 long loops = (timeout / 200L); 818 if (loops < 1) { 819 loops = 1; 820 } 821 for (int i = 0; i < loops; i++) { 822 if (info.hasData()) { 823 break; 824 } 825 try { 826 info.wait(200); 827 } catch (final InterruptedException e) { 828 /* Stub */ 829 } 830 } 831 } 832 } 833 834 /** 835 * {@inheritDoc} 836 */ 837 @Override 838 public void requestServiceInfo(String type, String name) { 839 this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); 840 } 841 842 /** 843 * {@inheritDoc} 844 */ 845 @Override 846 public void requestServiceInfo(String type, String name, boolean persistent) { 847 this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); 848 } 849 850 /** 851 * {@inheritDoc} 852 */ 853 @Override 854 public void requestServiceInfo(String type, String name, long timeout) { 855 this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); 856 } 857 858 /** 859 * {@inheritDoc} 860 */ 861 @Override 862 public void requestServiceInfo(String type, String name, boolean persistent, long timeout) { 863 final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent); 864 this.waitForInfoData(info, timeout); 865 } 866 867 void handleServiceResolved(ServiceEvent event) { 868 List<ServiceListenerStatus> list = _serviceListeners.get(event.getType().toLowerCase()); 869 final List<ServiceListenerStatus> listCopy; 870 if ((list != null) && (!list.isEmpty())) { 871 if ((event.getInfo() != null) && event.getInfo().hasData()) { 872 final ServiceEvent localEvent = event; 873 synchronized (list) { 874 listCopy = new ArrayList<ServiceListenerStatus>(list); 875 } 876 for (final ServiceListenerStatus listener : listCopy) { 877 _executor.submit(new Runnable() { 878 /** {@inheritDoc} */ 879 @Override 880 public void run() { 881 listener.serviceResolved(localEvent); 882 } 883 }); 884 } 885 } 886 } 887 } 888 889 /** 890 * {@inheritDoc} 891 */ 892 @Override 893 public void addServiceTypeListener(ServiceTypeListener listener) throws IOException { 894 ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); 895 _typeListeners.add(status); 896 897 // report cached service types 898 for (String type : _serviceTypes.keySet()) { 899 status.serviceTypeAdded(new ServiceEventImpl(this, type, "", null)); 900 } 901 902 this.startTypeResolver(); 903 } 904 905 /** 906 * {@inheritDoc} 907 */ 908 @Override 909 public void removeServiceTypeListener(ServiceTypeListener listener) { 910 ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); 911 _typeListeners.remove(status); 912 } 913 914 /** 915 * {@inheritDoc} 916 */ 917 @Override 918 public void addServiceListener(String type, ServiceListener listener) { 919 this.addServiceListener(type, listener, ListenerStatus.ASYNCHONEOUS); 920 } 921 922 private void addServiceListener(String type, ServiceListener listener, boolean synch) { 923 ServiceListenerStatus status = new ServiceListenerStatus(listener, synch); 924 final String loType = type.toLowerCase(); 925 List<ServiceListenerStatus> list = _serviceListeners.get(loType); 926 if (list == null) { 927 if (_serviceListeners.putIfAbsent(loType, new LinkedList<ServiceListenerStatus>()) == null) { 928 if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) { 929 // We have a problem here. The service collectors must be called synchronously so that their cache get cleaned up immediately or we will report . 930 this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS); 931 } 932 } 933 list = _serviceListeners.get(loType); 934 } 935 if (list != null) { 936 synchronized (list) { 937 if (!list.contains(listener)) { 938 list.add(status); 939 } 940 } 941 } 942 // report cached service types 943 final List<ServiceEvent> serviceEvents = new ArrayList<ServiceEvent>(); 944 Collection<DNSEntry> dnsEntryLits = this.getCache().allValues(); 945 for (DNSEntry entry : dnsEntryLits) { 946 final DNSRecord record = (DNSRecord) entry; 947 if (record.getRecordType() == DNSRecordType.TYPE_SRV) { 948 if (record.getKey().endsWith(loType)) { 949 // Do not used the record embedded method for generating event this will not work. 950 // serviceEvents.add(record.getServiceEvent(this)); 951 serviceEvents.add(new ServiceEventImpl(this, record.getType(), toUnqualifiedName(record.getType(), record.getName()), record.getServiceInfo())); 952 } 953 } 954 } 955 // Actually call listener with all service events added above 956 for (ServiceEvent serviceEvent : serviceEvents) { 957 status.serviceAdded(serviceEvent); 958 } 959 // Create/start ServiceResolver 960 this.startServiceResolver(type); 961 } 962 963 /** 964 * {@inheritDoc} 965 */ 966 @Override 967 public void removeServiceListener(String type, ServiceListener listener) { 968 String loType = type.toLowerCase(); 969 List<ServiceListenerStatus> list = _serviceListeners.get(loType); 970 if (list != null) { 971 synchronized (list) { 972 ServiceListenerStatus status = new ServiceListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); 973 list.remove(status); 974 if (list.isEmpty()) { 975 _serviceListeners.remove(loType, list); 976 } 977 } 978 } 979 } 980 981 /** 982 * {@inheritDoc} 983 */ 984 @Override 985 public void registerService(ServiceInfo infoAbstract) throws IOException { 986 if (this.isClosing() || this.isClosed()) { 987 throw new IllegalStateException("This DNS is closed."); 988 } 989 final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; 990 991 if (info.getDns() != null) { 992 if (info.getDns() != this) { 993 throw new IllegalStateException("A service information can only be registered with a single instamce of JmDNS."); 994 } else if (_services.get(info.getKey()) != null) { 995 throw new IllegalStateException("A service information can only be registered once."); 996 } 997 } 998 info.setDns(this); 999 1000 this.registerServiceType(info.getTypeWithSubtype()); 1001 1002 // bind the service to this address 1003 info.recoverState(); 1004 info.setServer(_localHost.getName()); 1005 info.addAddress(_localHost.getInet4Address()); 1006 info.addAddress(_localHost.getInet6Address()); 1007 1008 this.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT); 1009 1010 this.makeServiceNameUnique(info); 1011 while (_services.putIfAbsent(info.getKey(), info) != null) { 1012 this.makeServiceNameUnique(info); 1013 } 1014 1015 this.startProber(); 1016 info.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT); 1017 1018 if (logger.isLoggable(Level.FINE)) { 1019 logger.fine("registerService() JmDNS registered service as " + info); 1020 } 1021 } 1022 1023 /** 1024 * {@inheritDoc} 1025 */ 1026 @Override 1027 public void unregisterService(ServiceInfo infoAbstract) { 1028 final ServiceInfoImpl info = (ServiceInfoImpl) _services.get(infoAbstract.getKey()); 1029 1030 if (info != null) { 1031 info.cancelState(); 1032 this.startCanceler(); 1033 info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 1034 1035 _services.remove(info.getKey(), info); 1036 if (logger.isLoggable(Level.FINE)) { 1037 logger.fine("unregisterService() JmDNS unregistered service as " + info); 1038 } 1039 } else { 1040 logger.warning("Removing unregistered service info: " + infoAbstract.getKey()); 1041 } 1042 } 1043 1044 /** 1045 * {@inheritDoc} 1046 */ 1047 @Override 1048 public void unregisterAllServices() { 1049 if (logger.isLoggable(Level.FINER)) { 1050 logger.finer("unregisterAllServices()"); 1051 } 1052 1053 for (String name : _services.keySet()) { 1054 ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name); 1055 if (info != null) { 1056 if (logger.isLoggable(Level.FINER)) { 1057 logger.finer("Cancelling service info: " + info); 1058 } 1059 info.cancelState(); 1060 } 1061 } 1062 this.startCanceler(); 1063 1064 for (String name : _services.keySet()) { 1065 ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name); 1066 if (info != null) { 1067 if (logger.isLoggable(Level.FINER)) { 1068 logger.finer("Wait for service info cancel: " + info); 1069 } 1070 info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 1071 _services.remove(name, info); 1072 } 1073 } 1074 1075 } 1076 1077 /** 1078 * {@inheritDoc} 1079 */ 1080 @Override 1081 public boolean registerServiceType(String type) { 1082 boolean typeAdded = false; 1083 Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); 1084 String domain = map.get(Fields.Domain); 1085 String protocol = map.get(Fields.Protocol); 1086 String application = map.get(Fields.Application); 1087 String subtype = map.get(Fields.Subtype); 1088 1089 final String name = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 1090 final String loname = name.toLowerCase(); 1091 if (logger.isLoggable(Level.FINE)) { 1092 logger.fine(this.getName() + ".registering service type: " + type + " as: " + name + (subtype.length() > 0 ? " subtype: " + subtype : "")); 1093 } 1094 if (!_serviceTypes.containsKey(loname) && !application.toLowerCase().equals("dns-sd") && !domain.toLowerCase().endsWith("in-addr.arpa") && !domain.toLowerCase().endsWith("ip6.arpa")) { 1095 typeAdded = _serviceTypes.putIfAbsent(loname, new ServiceTypeEntry(name)) == null; 1096 if (typeAdded) { 1097 final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]); 1098 final ServiceEvent event = new ServiceEventImpl(this, name, "", null); 1099 for (final ServiceTypeListenerStatus status : list) { 1100 _executor.submit(new Runnable() { 1101 /** {@inheritDoc} */ 1102 @Override 1103 public void run() { 1104 status.serviceTypeAdded(event); 1105 } 1106 }); 1107 } 1108 } 1109 } 1110 if (subtype.length() > 0) { 1111 ServiceTypeEntry subtypes = _serviceTypes.get(loname); 1112 if ((subtypes != null) && (!subtypes.contains(subtype))) { 1113 synchronized (subtypes) { 1114 if (!subtypes.contains(subtype)) { 1115 typeAdded = true; 1116 subtypes.add(subtype); 1117 final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]); 1118 final ServiceEvent event = new ServiceEventImpl(this, "_" + subtype + "._sub." + name, "", null); 1119 for (final ServiceTypeListenerStatus status : list) { 1120 _executor.submit(new Runnable() { 1121 /** {@inheritDoc} */ 1122 @Override 1123 public void run() { 1124 status.subTypeForServiceTypeAdded(event); 1125 } 1126 }); 1127 } 1128 } 1129 } 1130 } 1131 } 1132 return typeAdded; 1133 } 1134 1135 /** 1136 * Generate a possibly unique name for a service using the information we have in the cache. 1137 * 1138 * @return returns true, if the name of the service info had to be changed. 1139 */ 1140 private boolean makeServiceNameUnique(ServiceInfoImpl info) { 1141 final String originalQualifiedName = info.getKey(); 1142 final long now = System.currentTimeMillis(); 1143 1144 boolean collision; 1145 do { 1146 collision = false; 1147 1148 // Check for collision in cache 1149 for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(info.getKey())) { 1150 if (DNSRecordType.TYPE_SRV.equals(dnsEntry.getRecordType()) && !dnsEntry.isExpired(now)) { 1151 final DNSRecord.Service s = (DNSRecord.Service) dnsEntry; 1152 if (s.getPort() != info.getPort() || !s.getServer().equals(_localHost.getName())) { 1153 if (logger.isLoggable(Level.FINER)) { 1154 logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + dnsEntry + " s.server=" + s.getServer() + " " + _localHost.getName() + " equals:" + (s.getServer().equals(_localHost.getName()))); 1155 } 1156 info.setName(incrementName(info.getName())); 1157 collision = true; 1158 break; 1159 } 1160 } 1161 } 1162 1163 // Check for collision with other service infos published by JmDNS 1164 final ServiceInfo selfService = _services.get(info.getKey()); 1165 if (selfService != null && selfService != info) { 1166 info.setName(incrementName(info.getName())); 1167 collision = true; 1168 } 1169 } 1170 while (collision); 1171 1172 return !(originalQualifiedName.equals(info.getKey())); 1173 } 1174 1175 String incrementName(String name) { 1176 String aName = name; 1177 try { 1178 final int l = aName.lastIndexOf('('); 1179 final int r = aName.lastIndexOf(')'); 1180 if ((l >= 0) && (l < r)) { 1181 aName = aName.substring(0, l) + "(" + (Integer.parseInt(aName.substring(l + 1, r)) + 1) + ")"; 1182 } else { 1183 aName += " (2)"; 1184 } 1185 } catch (final NumberFormatException e) { 1186 aName += " (2)"; 1187 } 1188 return aName; 1189 } 1190 1191 /** 1192 * Add a listener for a question. The listener will receive updates of answers to the question as they arrive, or from the cache if they are already available. 1193 * 1194 * @param listener 1195 * DSN listener 1196 * @param question 1197 * DNS query 1198 */ 1199 public void addListener(DNSListener listener, DNSQuestion question) { 1200 final long now = System.currentTimeMillis(); 1201 1202 // add the new listener 1203 _listeners.add(listener); 1204 1205 // report existing matched records 1206 1207 if (question != null) { 1208 for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(question.getName().toLowerCase())) { 1209 if (question.answeredBy(dnsEntry) && !dnsEntry.isExpired(now)) { 1210 listener.updateRecord(this.getCache(), now, dnsEntry); 1211 } 1212 } 1213 } 1214 } 1215 1216 /** 1217 * Remove a listener from all outstanding questions. The listener will no longer receive any updates. 1218 * 1219 * @param listener 1220 * DSN listener 1221 */ 1222 public void removeListener(DNSListener listener) { 1223 _listeners.remove(listener); 1224 } 1225 1226 /** 1227 * Renew a service when the record become stale. If there is no service collector for the type this method does nothing. 1228 * 1229 * @param record 1230 * DNS record 1231 */ 1232 public void renewServiceCollector(DNSRecord record) { 1233 ServiceInfo info = record.getServiceInfo(); 1234 if (_serviceCollectors.containsKey(info.getType().toLowerCase())) { 1235 // Create/start ServiceResolver 1236 this.startServiceResolver(info.getType()); 1237 } 1238 } 1239 1240 // Remind: Method updateRecord should receive a better name. 1241 /** 1242 * Notify all listeners that a record was updated. 1243 * 1244 * @param now 1245 * update date 1246 * @param rec 1247 * DNS record 1248 * @param operation 1249 * DNS cache operation 1250 */ 1251 public void updateRecord(long now, DNSRecord rec, Operation operation) { 1252 // We do not want to block the entire DNS while we are updating the record for each listener (service info) 1253 { 1254 List<DNSListener> listenerList = null; 1255 synchronized (_listeners) { 1256 listenerList = new ArrayList<DNSListener>(_listeners); 1257 } 1258 for (DNSListener listener : listenerList) { 1259 listener.updateRecord(this.getCache(), now, rec); 1260 } 1261 } 1262 if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType())) 1263 // if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType()) || DNSRecordType.TYPE_SRV.equals(rec.getRecordType())) 1264 { 1265 ServiceEvent event = rec.getServiceEvent(this); 1266 if ((event.getInfo() == null) || !event.getInfo().hasData()) { 1267 // We do not care about the subtype because the info is only used if complete and the subtype will then be included. 1268 ServiceInfo info = this.getServiceInfoFromCache(event.getType(), event.getName(), "", false); 1269 if (info.hasData()) { 1270 event = new ServiceEventImpl(this, event.getType(), event.getName(), info); 1271 } 1272 } 1273 1274 List<ServiceListenerStatus> list = _serviceListeners.get(event.getType().toLowerCase()); 1275 final List<ServiceListenerStatus> serviceListenerList; 1276 if (list != null) { 1277 synchronized (list) { 1278 serviceListenerList = new ArrayList<ServiceListenerStatus>(list); 1279 } 1280 } else { 1281 serviceListenerList = Collections.emptyList(); 1282 } 1283 if (logger.isLoggable(Level.FINEST)) { 1284 logger.finest(this.getName() + ".updating record for event: " + event + " list " + serviceListenerList + " operation: " + operation); 1285 } 1286 if (!serviceListenerList.isEmpty()) { 1287 final ServiceEvent localEvent = event; 1288 1289 switch (operation) { 1290 case Add: 1291 for (final ServiceListenerStatus listener : serviceListenerList) { 1292 if (listener.isSynchronous()) { 1293 listener.serviceAdded(localEvent); 1294 } else { 1295 _executor.submit(new Runnable() { 1296 /** {@inheritDoc} */ 1297 @Override 1298 public void run() { 1299 listener.serviceAdded(localEvent); 1300 } 1301 }); 1302 } 1303 } 1304 break; 1305 case Remove: 1306 for (final ServiceListenerStatus listener : serviceListenerList) { 1307 if (listener.isSynchronous()) { 1308 listener.serviceRemoved(localEvent); 1309 } else { 1310 _executor.submit(new Runnable() { 1311 /** {@inheritDoc} */ 1312 @Override 1313 public void run() { 1314 listener.serviceRemoved(localEvent); 1315 } 1316 }); 1317 } 1318 } 1319 break; 1320 default: 1321 break; 1322 } 1323 } 1324 } 1325 } 1326 1327 void handleRecord(DNSRecord record, long now) { 1328 DNSRecord newRecord = record; 1329 1330 Operation cacheOperation = Operation.Noop; 1331 final boolean expired = newRecord.isExpired(now); 1332 if (logger.isLoggable(Level.FINE)) { 1333 logger.fine(this.getName() + " handle response: " + newRecord); 1334 } 1335 1336 // update the cache 1337 if (!newRecord.isServicesDiscoveryMetaQuery() && !newRecord.isDomainDiscoveryQuery()) { 1338 final boolean unique = newRecord.isUnique(); 1339 final DNSRecord cachedRecord = (DNSRecord) this.getCache().getDNSEntry(newRecord); 1340 if (logger.isLoggable(Level.FINE)) { 1341 logger.fine(this.getName() + " handle response cached record: " + cachedRecord); 1342 } 1343 if (unique) { 1344 for (DNSEntry entry : this.getCache().getDNSEntryList(newRecord.getKey())) { 1345 if (newRecord.getRecordType().equals(entry.getRecordType()) && newRecord.getRecordClass().equals(entry.getRecordClass()) && (entry != cachedRecord)) { 1346 ((DNSRecord) entry).setWillExpireSoon(now); 1347 } 1348 } 1349 } 1350 if (cachedRecord != null) { 1351 if (expired) { 1352 // if the record has a 0 ttl that means we have a cancel record we need to delay the removal by 1s 1353 if (newRecord.getTTL() == 0) { 1354 cacheOperation = Operation.Noop; 1355 cachedRecord.setWillExpireSoon(now); 1356 // the actual record will be disposed of by the record reaper. 1357 } else { 1358 cacheOperation = Operation.Remove; 1359 this.getCache().removeDNSEntry(cachedRecord); 1360 } 1361 } else { 1362 // If the record content has changed we need to inform our listeners. 1363 if (!newRecord.sameValue(cachedRecord) || (!newRecord.sameSubtype(cachedRecord) && (newRecord.getSubtype().length() > 0))) { 1364 if (newRecord.isSingleValued()) { 1365 cacheOperation = Operation.Update; 1366 this.getCache().replaceDNSEntry(newRecord, cachedRecord); 1367 } else { 1368 // Address record can have more than one value on multi-homed machines 1369 cacheOperation = Operation.Add; 1370 this.getCache().addDNSEntry(newRecord); 1371 } 1372 } else { 1373 cachedRecord.resetTTL(newRecord); 1374 newRecord = cachedRecord; 1375 } 1376 } 1377 } else { 1378 if (!expired) { 1379 cacheOperation = Operation.Add; 1380 this.getCache().addDNSEntry(newRecord); 1381 } 1382 } 1383 } 1384 1385 // Register new service types 1386 if (newRecord.getRecordType() == DNSRecordType.TYPE_PTR) { 1387 // handle DNSConstants.DNS_META_QUERY records 1388 boolean typeAdded = false; 1389 if (newRecord.isServicesDiscoveryMetaQuery()) { 1390 // The service names are in the alias. 1391 if (!expired) { 1392 typeAdded = this.registerServiceType(((DNSRecord.Pointer) newRecord).getAlias()); 1393 } 1394 return; 1395 } 1396 typeAdded |= this.registerServiceType(newRecord.getName()); 1397 if (typeAdded && (cacheOperation == Operation.Noop)) { 1398 cacheOperation = Operation.RegisterServiceType; 1399 } 1400 } 1401 1402 // notify the listeners 1403 if (cacheOperation != Operation.Noop) { 1404 this.updateRecord(now, newRecord, cacheOperation); 1405 } 1406 1407 } 1408 1409 /** 1410 * Handle an incoming response. Cache answers, and pass them on to the appropriate questions. 1411 * 1412 * @exception IOException 1413 */ 1414 void handleResponse(DNSIncoming msg) throws IOException { 1415 final long now = System.currentTimeMillis(); 1416 1417 boolean hostConflictDetected = false; 1418 boolean serviceConflictDetected = false; 1419 1420 for (DNSRecord newRecord : msg.getAllAnswers()) { 1421 this.handleRecord(newRecord, now); 1422 1423 if (DNSRecordType.TYPE_A.equals(newRecord.getRecordType()) || DNSRecordType.TYPE_AAAA.equals(newRecord.getRecordType())) { 1424 hostConflictDetected |= newRecord.handleResponse(this); 1425 } else { 1426 serviceConflictDetected |= newRecord.handleResponse(this); 1427 } 1428 1429 } 1430 1431 if (hostConflictDetected || serviceConflictDetected) { 1432 this.startProber(); 1433 } 1434 } 1435 1436 /** 1437 * Handle an incoming query. See if we can answer any part of it given our service infos. 1438 * 1439 * @param in 1440 * @param addr 1441 * @param port 1442 * @exception IOException 1443 */ 1444 void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException { 1445 if (logger.isLoggable(Level.FINE)) { 1446 logger.fine(this.getName() + ".handle query: " + in); 1447 } 1448 // Track known answers 1449 boolean conflictDetected = false; 1450 final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL; 1451 for (DNSRecord answer : in.getAllAnswers()) { 1452 conflictDetected |= answer.handleQuery(this, expirationTime); 1453 } 1454 1455 this.ioLock(); 1456 try { 1457 1458 if (_plannedAnswer != null) { 1459 _plannedAnswer.append(in); 1460 } else { 1461 DNSIncoming plannedAnswer = in.clone(); 1462 if (in.isTruncated()) { 1463 _plannedAnswer = plannedAnswer; 1464 } 1465 this.startResponder(plannedAnswer, port); 1466 } 1467 1468 } finally { 1469 this.ioUnlock(); 1470 } 1471 1472 final long now = System.currentTimeMillis(); 1473 for (DNSRecord answer : in.getAnswers()) { 1474 this.handleRecord(answer, now); 1475 } 1476 1477 if (conflictDetected) { 1478 this.startProber(); 1479 } 1480 } 1481 1482 public void respondToQuery(DNSIncoming in) { 1483 this.ioLock(); 1484 try { 1485 if (_plannedAnswer == in) { 1486 _plannedAnswer = null; 1487 } 1488 } finally { 1489 this.ioUnlock(); 1490 } 1491 } 1492 1493 /** 1494 * Add an answer to a question. Deal with the case when the outgoing packet overflows 1495 * 1496 * @param in 1497 * @param addr 1498 * @param port 1499 * @param out 1500 * @param rec 1501 * @return outgoing answer 1502 * @exception IOException 1503 */ 1504 public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException { 1505 DNSOutgoing newOut = out; 1506 if (newOut == null) { 1507 newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload()); 1508 } 1509 try { 1510 newOut.addAnswer(in, rec); 1511 } catch (final IOException e) { 1512 newOut.setFlags(newOut.getFlags() | DNSConstants.FLAGS_TC); 1513 newOut.setId(in.getId()); 1514 send(newOut); 1515 1516 newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload()); 1517 newOut.addAnswer(in, rec); 1518 } 1519 return newOut; 1520 } 1521 1522 /** 1523 * Send an outgoing multicast DNS message. 1524 * 1525 * @param out 1526 * @exception IOException 1527 */ 1528 public void send(DNSOutgoing out) throws IOException { 1529 if (!out.isEmpty()) { 1530 byte[] message = out.data(); 1531 final DatagramPacket packet = new DatagramPacket(message, message.length, _group, DNSConstants.MDNS_PORT); 1532 1533 if (logger.isLoggable(Level.FINEST)) { 1534 try { 1535 final DNSIncoming msg = new DNSIncoming(packet); 1536 if (logger.isLoggable(Level.FINEST)) { 1537 logger.finest("send(" + this.getName() + ") JmDNS out:" + msg.print(true)); 1538 } 1539 } catch (final IOException e) { 1540 logger.throwing(getClass().toString(), "send(" + this.getName() + ") - JmDNS can not parse what it sends!!!", e); 1541 } 1542 } 1543 final MulticastSocket ms = _socket; 1544 if (ms != null && !ms.isClosed()) { 1545 ms.send(packet); 1546 } 1547 } 1548 } 1549 1550 /* 1551 * (non-Javadoc) 1552 * @see javax.jmdns.impl.DNSTaskStarter#purgeTimer() 1553 */ 1554 @Override 1555 public void purgeTimer() { 1556 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeTimer(); 1557 } 1558 1559 /* 1560 * (non-Javadoc) 1561 * @see javax.jmdns.impl.DNSTaskStarter#purgeStateTimer() 1562 */ 1563 @Override 1564 public void purgeStateTimer() { 1565 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeStateTimer(); 1566 } 1567 1568 /* 1569 * (non-Javadoc) 1570 * @see javax.jmdns.impl.DNSTaskStarter#cancelTimer() 1571 */ 1572 @Override 1573 public void cancelTimer() { 1574 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelTimer(); 1575 } 1576 1577 /* 1578 * (non-Javadoc) 1579 * @see javax.jmdns.impl.DNSTaskStarter#cancelStateTimer() 1580 */ 1581 @Override 1582 public void cancelStateTimer() { 1583 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelStateTimer(); 1584 } 1585 1586 /* 1587 * (non-Javadoc) 1588 * @see javax.jmdns.impl.DNSTaskStarter#startProber() 1589 */ 1590 @Override 1591 public void startProber() { 1592 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startProber(); 1593 } 1594 1595 /* 1596 * (non-Javadoc) 1597 * @see javax.jmdns.impl.DNSTaskStarter#startAnnouncer() 1598 */ 1599 @Override 1600 public void startAnnouncer() { 1601 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startAnnouncer(); 1602 } 1603 1604 /* 1605 * (non-Javadoc) 1606 * @see javax.jmdns.impl.DNSTaskStarter#startRenewer() 1607 */ 1608 @Override 1609 public void startRenewer() { 1610 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startRenewer(); 1611 } 1612 1613 /* 1614 * (non-Javadoc) 1615 * @see javax.jmdns.impl.DNSTaskStarter#startCanceler() 1616 */ 1617 @Override 1618 public void startCanceler() { 1619 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startCanceler(); 1620 } 1621 1622 /* 1623 * (non-Javadoc) 1624 * @see javax.jmdns.impl.DNSTaskStarter#startReaper() 1625 */ 1626 @Override 1627 public void startReaper() { 1628 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startReaper(); 1629 } 1630 1631 /* 1632 * (non-Javadoc) 1633 * @see javax.jmdns.impl.DNSTaskStarter#startServiceInfoResolver(javax.jmdns.impl.ServiceInfoImpl) 1634 */ 1635 @Override 1636 public void startServiceInfoResolver(ServiceInfoImpl info) { 1637 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceInfoResolver(info); 1638 } 1639 1640 /* 1641 * (non-Javadoc) 1642 * @see javax.jmdns.impl.DNSTaskStarter#startTypeResolver() 1643 */ 1644 @Override 1645 public void startTypeResolver() { 1646 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startTypeResolver(); 1647 } 1648 1649 /* 1650 * (non-Javadoc) 1651 * @see javax.jmdns.impl.DNSTaskStarter#startServiceResolver(java.lang.String) 1652 */ 1653 @Override 1654 public void startServiceResolver(String type) { 1655 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceResolver(type); 1656 } 1657 1658 /* 1659 * (non-Javadoc) 1660 * @see javax.jmdns.impl.DNSTaskStarter#startResponder(javax.jmdns.impl.DNSIncoming, int) 1661 */ 1662 @Override 1663 public void startResponder(DNSIncoming in, int port) { 1664 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startResponder(in, port); 1665 } 1666 1667 // REMIND: Why is this not an anonymous inner class? 1668 /** 1669 * Shutdown operations. 1670 */ 1671 protected class Shutdown implements Runnable { 1672 /** {@inheritDoc} */ 1673 @Override 1674 public void run() { 1675 try { 1676 _shutdown = null; 1677 close(); 1678 } catch (Throwable exception) { 1679 System.err.println("Error while shuting down. " + exception); 1680 } 1681 } 1682 } 1683 1684 private final Object _recoverLock = new Object(); 1685 1686 /** 1687 * Recover jmdns when there is an error. 1688 */ 1689 public void recover() { 1690 logger.finer(this.getName() + "recover()"); 1691 // We have an IO error so lets try to recover if anything happens lets close it. 1692 // This should cover the case of the IP address changing under our feet 1693 if (this.isClosing() || this.isClosed() || this.isCanceling() || this.isCanceled()) { 1694 return; 1695 } 1696 1697 // We need some definite lock here as we may have multiple timer running in the same thread that will not be stopped by the reentrant lock 1698 // in the state object. This is only a problem in this case as we are going to execute in seperate thread so that the timer can clear. 1699 synchronized (_recoverLock) { 1700 // Stop JmDNS 1701 // This protects against recursive calls 1702 if (this.cancelState()) { 1703 logger.finer(this.getName() + "recover() thread " + Thread.currentThread().getName()); 1704 Thread recover = new Thread(this.getName() + ".recover()") { 1705 /** 1706 * {@inheritDoc} 1707 */ 1708 @Override 1709 public void run() { 1710 __recover(); 1711 } 1712 }; 1713 recover.start(); 1714 } 1715 } 1716 } 1717 1718 void __recover() { 1719 // Synchronize only if we are not already in process to prevent dead locks 1720 // 1721 if (logger.isLoggable(Level.FINER)) { 1722 logger.finer(this.getName() + "recover() Cleanning up"); 1723 } 1724 1725 logger.warning("RECOVERING"); 1726 // Purge the timer 1727 this.purgeTimer(); 1728 1729 // We need to keep a copy for reregistration 1730 final Collection<ServiceInfo> oldServiceInfos = new ArrayList<ServiceInfo>(getServices().values()); 1731 1732 // Cancel all services 1733 this.unregisterAllServices(); 1734 this.disposeServiceCollectors(); 1735 1736 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 1737 1738 // Purge the canceler timer 1739 this.purgeStateTimer(); 1740 1741 // 1742 // close multicast socket 1743 this.closeMulticastSocket(); 1744 1745 // 1746 this.getCache().clear(); 1747 if (logger.isLoggable(Level.FINER)) { 1748 logger.finer(this.getName() + "recover() All is clean"); 1749 } 1750 1751 if (this.isCanceled()) { 1752 // 1753 // All is clear now start the services 1754 // 1755 for (ServiceInfo info : oldServiceInfos) { 1756 ((ServiceInfoImpl) info).recoverState(); 1757 } 1758 this.recoverState(); 1759 1760 try { 1761 this.openMulticastSocket(this.getLocalHost()); 1762 this.start(oldServiceInfos); 1763 } catch (final Exception exception) { 1764 logger.log(Level.WARNING, this.getName() + "recover() Start services exception ", exception); 1765 } 1766 logger.log(Level.WARNING, this.getName() + "recover() We are back!"); 1767 } else { 1768 // We have a problem. We could not clear the state. 1769 logger.log(Level.WARNING, this.getName() + "recover() Could not recover we are Down!"); 1770 if (this.getDelegate() != null) { 1771 this.getDelegate().cannotRecoverFromIOError(this.getDns(), oldServiceInfos); 1772 } 1773 } 1774 1775 } 1776 1777 public void cleanCache() { 1778 long now = System.currentTimeMillis(); 1779 for (DNSEntry entry : this.getCache().allValues()) { 1780 try { 1781 DNSRecord record = (DNSRecord) entry; 1782 if (record.isExpired(now)) { 1783 this.updateRecord(now, record, Operation.Remove); 1784 this.getCache().removeDNSEntry(record); 1785 } else if (record.isStale(now)) { 1786 // we should query for the record we care about i.e. those in the service collectors 1787 this.renewServiceCollector(record); 1788 } 1789 } catch (Exception exception) { 1790 logger.log(Level.SEVERE, this.getName() + ".Error while reaping records: " + entry, exception); 1791 logger.severe(this.toString()); 1792 } 1793 } 1794 } 1795 1796 /** 1797 * {@inheritDoc} 1798 */ 1799 @Override 1800 public void close() { 1801 if (this.isClosing()) { 1802 return; 1803 } 1804 1805 if (logger.isLoggable(Level.FINER)) { 1806 logger.finer("Cancelling JmDNS: " + this); 1807 } 1808 // Stop JmDNS 1809 // This protects against recursive calls 1810 if (this.closeState()) { 1811 // We got the tie break now clean up 1812 1813 // Stop the timer 1814 logger.finer("Canceling the timer"); 1815 this.cancelTimer(); 1816 1817 // Cancel all services 1818 this.unregisterAllServices(); 1819 this.disposeServiceCollectors(); 1820 1821 if (logger.isLoggable(Level.FINER)) { 1822 logger.finer("Wait for JmDNS cancel: " + this); 1823 } 1824 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 1825 1826 // Stop the canceler timer 1827 logger.finer("Canceling the state timer"); 1828 this.cancelStateTimer(); 1829 1830 // Stop the executor 1831 _executor.shutdown(); 1832 1833 // close socket 1834 this.closeMulticastSocket(); 1835 1836 // remove the shutdown hook 1837 if (_shutdown != null) { 1838 Runtime.getRuntime().removeShutdownHook(_shutdown); 1839 } 1840 1841 if (logger.isLoggable(Level.FINER)) { 1842 logger.finer("JmDNS closed."); 1843 } 1844 } 1845 advanceState(null); 1846 } 1847 1848 /** 1849 * {@inheritDoc} 1850 */ 1851 @Override 1852 @Deprecated 1853 public void printServices() { 1854 System.err.println(toString()); 1855 } 1856 1857 /** 1858 * {@inheritDoc} 1859 */ 1860 @Override 1861 public String toString() { 1862 final StringBuilder aLog = new StringBuilder(2048); 1863 aLog.append("\t---- Local Host -----"); 1864 aLog.append("\n\t"); 1865 aLog.append(_localHost); 1866 aLog.append("\n\t---- Services -----"); 1867 for (String key : _services.keySet()) { 1868 aLog.append("\n\t\tService: "); 1869 aLog.append(key); 1870 aLog.append(": "); 1871 aLog.append(_services.get(key)); 1872 } 1873 aLog.append("\n"); 1874 aLog.append("\t---- Types ----"); 1875 for (String key : _serviceTypes.keySet()) { 1876 ServiceTypeEntry subtypes = _serviceTypes.get(key); 1877 aLog.append("\n\t\tType: "); 1878 aLog.append(subtypes.getType()); 1879 aLog.append(": "); 1880 aLog.append(subtypes.isEmpty() ? "no subtypes" : subtypes); 1881 } 1882 aLog.append("\n"); 1883 aLog.append(_cache.toString()); 1884 aLog.append("\n"); 1885 aLog.append("\t---- Service Collectors ----"); 1886 for (String key : _serviceCollectors.keySet()) { 1887 aLog.append("\n\t\tService Collector: "); 1888 aLog.append(key); 1889 aLog.append(": "); 1890 aLog.append(_serviceCollectors.get(key)); 1891 } 1892 aLog.append("\n"); 1893 aLog.append("\t---- Service Listeners ----"); 1894 for (String key : _serviceListeners.keySet()) { 1895 aLog.append("\n\t\tService Listener: "); 1896 aLog.append(key); 1897 aLog.append(": "); 1898 aLog.append(_serviceListeners.get(key)); 1899 } 1900 return aLog.toString(); 1901 } 1902 1903 /** 1904 * {@inheritDoc} 1905 */ 1906 @Override 1907 public ServiceInfo[] list(String type) { 1908 return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT); 1909 } 1910 1911 /** 1912 * {@inheritDoc} 1913 */ 1914 @Override 1915 public ServiceInfo[] list(String type, long timeout) { 1916 this.cleanCache(); 1917 // Implementation note: The first time a list for a given type is 1918 // requested, a ServiceCollector is created which collects service 1919 // infos. This greatly speeds up the performance of subsequent calls 1920 // to this method. The caveats are, that 1) the first call to this 1921 // method for a given type is slow, and 2) we spawn a ServiceCollector 1922 // instance for each service type which increases network traffic a 1923 // little. 1924 1925 String loType = type.toLowerCase(); 1926 1927 boolean newCollectorCreated = false; 1928 if (this.isCanceling() || this.isCanceled()) { 1929 return new ServiceInfo[0]; 1930 } 1931 1932 ServiceCollector collector = _serviceCollectors.get(loType); 1933 if (collector == null) { 1934 newCollectorCreated = _serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null; 1935 collector = _serviceCollectors.get(loType); 1936 if (newCollectorCreated) { 1937 this.addServiceListener(type, collector, ListenerStatus.SYNCHONEOUS); 1938 } 1939 } 1940 if (logger.isLoggable(Level.FINER)) { 1941 logger.finer(this.getName() + ".collector: " + collector); 1942 } 1943 // At this stage the collector should never be null but it keeps findbugs happy. 1944 return (collector != null ? collector.list(timeout) : new ServiceInfo[0]); 1945 } 1946 1947 /** 1948 * {@inheritDoc} 1949 */ 1950 @Override 1951 public Map<String, ServiceInfo[]> listBySubtype(String type) { 1952 return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT); 1953 } 1954 1955 /** 1956 * {@inheritDoc} 1957 */ 1958 @Override 1959 public Map<String, ServiceInfo[]> listBySubtype(String type, long timeout) { 1960 Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5); 1961 for (ServiceInfo info : this.list(type, timeout)) { 1962 String subtype = info.getSubtype().toLowerCase(); 1963 if (!map.containsKey(subtype)) { 1964 map.put(subtype, new ArrayList<ServiceInfo>(10)); 1965 } 1966 map.get(subtype).add(info); 1967 } 1968 1969 Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size()); 1970 for (String subtype : map.keySet()) { 1971 List<ServiceInfo> infoForSubType = map.get(subtype); 1972 result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()])); 1973 } 1974 1975 return result; 1976 } 1977 1978 /** 1979 * This method disposes all ServiceCollector instances which have been created by calls to method <code>list(type)</code>. 1980 * 1981 * @see #list 1982 */ 1983 private void disposeServiceCollectors() { 1984 if (logger.isLoggable(Level.FINER)) { 1985 logger.finer("disposeServiceCollectors()"); 1986 } 1987 for (String type : _serviceCollectors.keySet()) { 1988 ServiceCollector collector = _serviceCollectors.get(type); 1989 if (collector != null) { 1990 this.removeServiceListener(type, collector); 1991 _serviceCollectors.remove(type, collector); 1992 } 1993 } 1994 } 1995 1996 /** 1997 * Instances of ServiceCollector are used internally to speed up the performance of method <code>list(type)</code>. 1998 * 1999 * @see #list 2000 */ 2001 private static class ServiceCollector implements ServiceListener { 2002 // private static Logger logger = Logger.getLogger(ServiceCollector.class.getName()); 2003 2004 /** 2005 * A set of collected service instance names. 2006 */ 2007 private final ConcurrentMap<String, ServiceInfo> _infos; 2008 2009 /** 2010 * A set of collected service event waiting to be resolved. 2011 */ 2012 private final ConcurrentMap<String, ServiceEvent> _events; 2013 2014 /** 2015 * This is the type we are listening for (only used for debugging). 2016 */ 2017 private final String _type; 2018 2019 /** 2020 * This is used to force a wait on the first invocation of list. 2021 */ 2022 private volatile boolean _needToWaitForInfos; 2023 2024 public ServiceCollector(String type) { 2025 super(); 2026 _infos = new ConcurrentHashMap<String, ServiceInfo>(); 2027 _events = new ConcurrentHashMap<String, ServiceEvent>(); 2028 _type = type; 2029 _needToWaitForInfos = true; 2030 } 2031 2032 /** 2033 * A service has been added. 2034 * 2035 * @param event 2036 * service event 2037 */ 2038 @Override 2039 public void serviceAdded(ServiceEvent event) { 2040 synchronized (this) { 2041 ServiceInfo info = event.getInfo(); 2042 if ((info != null) && (info.hasData())) { 2043 _infos.put(event.getName(), info); 2044 } else { 2045 String subtype = (info != null ? info.getSubtype() : ""); 2046 info = ((JmDNSImpl) event.getDNS()).resolveServiceInfo(event.getType(), event.getName(), subtype, true); 2047 if (info != null) { 2048 _infos.put(event.getName(), info); 2049 } else { 2050 _events.put(event.getName(), event); 2051 } 2052 } 2053 } 2054 } 2055 2056 /** 2057 * A service has been removed. 2058 * 2059 * @param event 2060 * service event 2061 */ 2062 @Override 2063 public void serviceRemoved(ServiceEvent event) { 2064 synchronized (this) { 2065 _infos.remove(event.getName()); 2066 _events.remove(event.getName()); 2067 } 2068 } 2069 2070 /** 2071 * A service has been resolved. Its details are now available in the ServiceInfo record. 2072 * 2073 * @param event 2074 * service event 2075 */ 2076 @Override 2077 public void serviceResolved(ServiceEvent event) { 2078 synchronized (this) { 2079 _infos.put(event.getName(), event.getInfo()); 2080 _events.remove(event.getName()); 2081 } 2082 } 2083 2084 /** 2085 * Returns an array of all service infos which have been collected by this ServiceCollector. 2086 * 2087 * @param timeout 2088 * timeout if the info list is empty. 2089 * @return Service Info array 2090 */ 2091 public ServiceInfo[] list(long timeout) { 2092 if (_infos.isEmpty() || !_events.isEmpty() || _needToWaitForInfos) { 2093 long loops = (timeout / 200L); 2094 if (loops < 1) { 2095 loops = 1; 2096 } 2097 for (int i = 0; i < loops; i++) { 2098 try { 2099 Thread.sleep(200); 2100 } catch (final InterruptedException e) { 2101 /* Stub */ 2102 } 2103 if (_events.isEmpty() && !_infos.isEmpty() && !_needToWaitForInfos) { 2104 break; 2105 } 2106 } 2107 } 2108 _needToWaitForInfos = false; 2109 return _infos.values().toArray(new ServiceInfo[_infos.size()]); 2110 } 2111 2112 /** 2113 * {@inheritDoc} 2114 */ 2115 @Override 2116 public String toString() { 2117 final StringBuffer aLog = new StringBuffer(); 2118 aLog.append("\n\tType: "); 2119 aLog.append(_type); 2120 if (_infos.isEmpty()) { 2121 aLog.append("\n\tNo services collected."); 2122 } else { 2123 aLog.append("\n\tServices"); 2124 for (String key : _infos.keySet()) { 2125 aLog.append("\n\t\tService: "); 2126 aLog.append(key); 2127 aLog.append(": "); 2128 aLog.append(_infos.get(key)); 2129 } 2130 } 2131 if (_events.isEmpty()) { 2132 aLog.append("\n\tNo event queued."); 2133 } else { 2134 aLog.append("\n\tEvents"); 2135 for (String key : _events.keySet()) { 2136 aLog.append("\n\t\tEvent: "); 2137 aLog.append(key); 2138 aLog.append(": "); 2139 aLog.append(_events.get(key)); 2140 } 2141 } 2142 return aLog.toString(); 2143 } 2144 } 2145 2146 static String toUnqualifiedName(String type, String qualifiedName) { 2147 String loType = type.toLowerCase(); 2148 String loQualifiedName = qualifiedName.toLowerCase(); 2149 if (loQualifiedName.endsWith(loType) && !(loQualifiedName.equals(loType))) { 2150 return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1); 2151 } 2152 return qualifiedName; 2153 } 2154 2155 public Map<String, ServiceInfo> getServices() { 2156 return _services; 2157 } 2158 2159 public void setLastThrottleIncrement(long lastThrottleIncrement) { 2160 this._lastThrottleIncrement = lastThrottleIncrement; 2161 } 2162 2163 public long getLastThrottleIncrement() { 2164 return _lastThrottleIncrement; 2165 } 2166 2167 public void setThrottle(int throttle) { 2168 this._throttle = throttle; 2169 } 2170 2171 public int getThrottle() { 2172 return _throttle; 2173 } 2174 2175 public static Random getRandom() { 2176 return _random; 2177 } 2178 2179 public void ioLock() { 2180 _ioLock.lock(); 2181 } 2182 2183 public void ioUnlock() { 2184 _ioLock.unlock(); 2185 } 2186 2187 public void setPlannedAnswer(DNSIncoming plannedAnswer) { 2188 this._plannedAnswer = plannedAnswer; 2189 } 2190 2191 public DNSIncoming getPlannedAnswer() { 2192 return _plannedAnswer; 2193 } 2194 2195 void setLocalHost(HostInfo localHost) { 2196 this._localHost = localHost; 2197 } 2198 2199 public Map<String, ServiceTypeEntry> getServiceTypes() { 2200 return _serviceTypes; 2201 } 2202 2203 public MulticastSocket getSocket() { 2204 return _socket; 2205 } 2206 2207 public InetAddress getGroup() { 2208 return _group; 2209 } 2210 2211 @Override 2212 public Delegate getDelegate() { 2213 return this._delegate; 2214 } 2215 2216 @Override 2217 public Delegate setDelegate(Delegate delegate) { 2218 Delegate previous = this._delegate; 2219 this._delegate = delegate; 2220 return previous; 2221 } 2222 2223 } 2224