$search
00001 // /Copyright 2003-2005 Arthur van Hoff, Rick Blair 00002 // Licensed under Apache License version 2.0 00003 // Original license LGPL 00004 00005 package javax.jmdns.impl; 00006 00007 import java.io.IOException; 00008 import java.net.DatagramPacket; 00009 import java.net.Inet4Address; 00010 import java.net.Inet6Address; 00011 import java.net.InetAddress; 00012 import java.net.MulticastSocket; 00013 import java.net.SocketException; 00014 import java.util.AbstractMap; 00015 import java.util.ArrayList; 00016 import java.util.Arrays; 00017 import java.util.Collection; 00018 import java.util.Collections; 00019 import java.util.HashMap; 00020 import java.util.HashSet; 00021 import java.util.Iterator; 00022 import java.util.LinkedList; 00023 import java.util.List; 00024 import java.util.Map; 00025 import java.util.Properties; 00026 import java.util.Random; 00027 import java.util.Set; 00028 import java.util.concurrent.ConcurrentHashMap; 00029 import java.util.concurrent.ConcurrentMap; 00030 import java.util.concurrent.ExecutorService; 00031 import java.util.concurrent.Executors; 00032 import java.util.concurrent.locks.ReentrantLock; 00033 import java.util.logging.Level; 00034 import java.util.logging.Logger; 00035 00036 import javax.jmdns.JmDNS; 00037 import javax.jmdns.ServiceEvent; 00038 import javax.jmdns.ServiceInfo; 00039 import javax.jmdns.ServiceInfo.Fields; 00040 import javax.jmdns.ServiceListener; 00041 import javax.jmdns.ServiceTypeListener; 00042 import javax.jmdns.impl.ListenerStatus.ServiceListenerStatus; 00043 import javax.jmdns.impl.ListenerStatus.ServiceTypeListenerStatus; 00044 import javax.jmdns.impl.constants.DNSConstants; 00045 import javax.jmdns.impl.constants.DNSRecordClass; 00046 import javax.jmdns.impl.constants.DNSRecordType; 00047 import javax.jmdns.impl.constants.DNSState; 00048 import javax.jmdns.impl.tasks.DNSTask; 00049 00050 // REMIND: multiple IP addresses 00051 00057 public class JmDNSImpl extends JmDNS implements DNSStatefulObject, DNSTaskStarter { 00058 private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName()); 00059 00060 public enum Operation { 00061 Remove, Update, Add, RegisterServiceType, Noop 00062 } 00063 00067 private volatile InetAddress _group; 00071 private volatile MulticastSocket _socket; 00072 00076 private final List<DNSListener> _listeners; 00077 00081 private final ConcurrentMap<String, List<ServiceListenerStatus>> _serviceListeners; 00082 00086 private final Set<ServiceTypeListenerStatus> _typeListeners; 00087 00091 private final DNSCache _cache; 00092 00096 private final ConcurrentMap<String, ServiceInfo> _services; 00097 00103 private final ConcurrentMap<String, ServiceTypeEntry> _serviceTypes; 00104 00105 private volatile Delegate _delegate; 00106 00113 public static class ServiceTypeEntry extends AbstractMap<String, String> implements Cloneable { 00114 00115 private final Set<Map.Entry<String, String>> _entrySet; 00116 00117 private final String _type; 00118 00119 private static class SubTypeEntry implements Entry<String, String>, java.io.Serializable, Cloneable { 00120 00121 private static final long serialVersionUID = 9188503522395855322L; 00122 00123 private final String _key; 00124 private final String _value; 00125 00126 public SubTypeEntry(String subtype) { 00127 super(); 00128 _value = (subtype != null ? subtype : ""); 00129 _key = _value.toLowerCase(); 00130 } 00131 00135 @Override 00136 public String getKey() { 00137 return _key; 00138 } 00139 00143 @Override 00144 public String getValue() { 00145 return _value; 00146 } 00147 00157 @Override 00158 public String setValue(String value) { 00159 throw new UnsupportedOperationException(); 00160 } 00161 00165 @Override 00166 public boolean equals(Object entry) { 00167 if (!(entry instanceof Map.Entry)) { 00168 return false; 00169 } 00170 return this.getKey().equals(((Map.Entry<?, ?>) entry).getKey()) && this.getValue().equals(((Map.Entry<?, ?>) entry).getValue()); 00171 } 00172 00176 @Override 00177 public int hashCode() { 00178 return (_key == null ? 0 : _key.hashCode()) ^ (_value == null ? 0 : _value.hashCode()); 00179 } 00180 00181 /* 00182 * (non-Javadoc) 00183 * @see java.lang.Object#clone() 00184 */ 00185 @Override 00186 public SubTypeEntry clone() { 00187 // Immutable object 00188 return this; 00189 } 00190 00194 @Override 00195 public String toString() { 00196 return _key + "=" + _value; 00197 } 00198 00199 } 00200 00201 public ServiceTypeEntry(String type) { 00202 super(); 00203 this._type = type; 00204 this._entrySet = new HashSet<Map.Entry<String, String>>(); 00205 } 00206 00212 public String getType() { 00213 return _type; 00214 } 00215 00216 /* 00217 * (non-Javadoc) 00218 * @see java.util.AbstractMap#entrySet() 00219 */ 00220 @Override 00221 public Set<Map.Entry<String, String>> entrySet() { 00222 return _entrySet; 00223 } 00224 00233 public boolean contains(String subtype) { 00234 return subtype != null && this.containsKey(subtype.toLowerCase()); 00235 } 00236 00245 public boolean add(String subtype) { 00246 if (subtype == null || this.contains(subtype)) { 00247 return false; 00248 } 00249 _entrySet.add(new SubTypeEntry(subtype)); 00250 return true; 00251 } 00252 00258 public Iterator<String> iterator() { 00259 return this.keySet().iterator(); 00260 } 00261 00262 /* 00263 * (non-Javadoc) 00264 * @see java.util.AbstractMap#clone() 00265 */ 00266 @Override 00267 public ServiceTypeEntry clone() { 00268 ServiceTypeEntry entry = new ServiceTypeEntry(this.getType()); 00269 for (Map.Entry<String, String> subTypeEntry : this.entrySet()) { 00270 entry.add(subTypeEntry.getValue()); 00271 } 00272 return entry; 00273 } 00274 00275 /* 00276 * (non-Javadoc) 00277 * @see java.util.AbstractMap#toString() 00278 */ 00279 @Override 00280 public String toString() { 00281 final StringBuilder aLog = new StringBuilder(200); 00282 if (this.isEmpty()) { 00283 aLog.append("empty"); 00284 } else { 00285 for (String value : this.values()) { 00286 aLog.append(value); 00287 aLog.append(", "); 00288 } 00289 aLog.setLength(aLog.length() - 2); 00290 } 00291 return aLog.toString(); 00292 } 00293 00294 } 00295 00299 protected Thread _shutdown; 00300 00304 private HostInfo _localHost; 00305 00306 private Thread _incomingListener; 00307 00311 private int _throttle; 00312 00316 private long _lastThrottleIncrement; 00317 00318 private final ExecutorService _executor = Executors.newSingleThreadExecutor(); 00319 00320 // 00321 // 2009-09-16 ldeck: adding docbug patch with slight ammendments 00322 // 'Fixes two deadlock conditions involving JmDNS.close() - ID: 1473279' 00323 // 00324 // --------------------------------------------------- 00329 // private final Timer _cancelerTimer; 00330 // --------------------------------------------------- 00331 00335 private final static Random _random = new Random(); 00336 00340 private final ReentrantLock _ioLock = new ReentrantLock(); 00341 00346 private DNSIncoming _plannedAnswer; 00347 00348 // State machine 00349 00355 private final ConcurrentMap<String, ServiceCollector> _serviceCollectors; 00356 00357 private final String _name; 00358 00365 public static void main(String[] argv) { 00366 String version = null; 00367 try { 00368 final Properties pomProperties = new Properties(); 00369 pomProperties.load(JmDNSImpl.class.getResourceAsStream("/META-INF/maven/javax.jmdns/jmdns/pom.properties")); 00370 version = pomProperties.getProperty("version"); 00371 } catch (Exception e) { 00372 version = "RUNNING.IN.IDE.FULL"; 00373 } 00374 System.out.println("JmDNS version \"" + version + "\""); 00375 System.out.println(" "); 00376 00377 System.out.println("Running on java version \"" + System.getProperty("java.version") + "\"" + " (build " + System.getProperty("java.runtime.version") + ")" + " from " + System.getProperty("java.vendor")); 00378 00379 System.out.println("Operating environment \"" + System.getProperty("os.name") + "\"" + " version " + System.getProperty("os.version") + " on " + System.getProperty("os.arch")); 00380 00381 System.out.println("For more information on JmDNS please visit https://sourceforge.net/projects/jmdns/"); 00382 } 00383 00393 public JmDNSImpl(InetAddress address, String name) throws IOException { 00394 super(); 00395 if (logger.isLoggable(Level.FINER)) { 00396 logger.finer("JmDNS instance created"); 00397 } 00398 _cache = new DNSCache(100); 00399 00400 _listeners = Collections.synchronizedList(new ArrayList<DNSListener>()); 00401 _serviceListeners = new ConcurrentHashMap<String, List<ServiceListenerStatus>>(); 00402 _typeListeners = Collections.synchronizedSet(new HashSet<ServiceTypeListenerStatus>()); 00403 _serviceCollectors = new ConcurrentHashMap<String, ServiceCollector>(); 00404 00405 _services = new ConcurrentHashMap<String, ServiceInfo>(20); 00406 _serviceTypes = new ConcurrentHashMap<String, ServiceTypeEntry>(20); 00407 00408 _localHost = HostInfo.newHostInfo(address, this, name); 00409 _name = (name != null ? name : _localHost.getName()); 00410 00411 // _cancelerTimer = new Timer("JmDNS.cancelerTimer"); 00412 00413 // (ldeck 2.1.1) preventing shutdown blocking thread 00414 // ------------------------------------------------- 00415 // _shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown"); 00416 // Runtime.getRuntime().addShutdownHook(_shutdown); 00417 00418 // ------------------------------------------------- 00419 00420 // Bind to multicast socket 00421 this.openMulticastSocket(this.getLocalHost()); 00422 this.start(this.getServices().values()); 00423 00424 this.startReaper(); 00425 } 00426 00427 private void start(Collection<? extends ServiceInfo> serviceInfos) { 00428 if (_incomingListener == null) { 00429 _incomingListener = new SocketListener(this); 00430 _incomingListener.start(); 00431 } 00432 this.startProber(); 00433 for (ServiceInfo info : serviceInfos) { 00434 try { 00435 this.registerService(new ServiceInfoImpl(info)); 00436 } catch (final Exception exception) { 00437 logger.log(Level.WARNING, "start() Registration exception ", exception); 00438 } 00439 } 00440 } 00441 00442 private void openMulticastSocket(HostInfo hostInfo) throws IOException { 00443 if (_group == null) { 00444 if (hostInfo.getInetAddress() instanceof Inet6Address) { 00445 _group = InetAddress.getByName(DNSConstants.MDNS_GROUP_IPV6); 00446 } else { 00447 _group = InetAddress.getByName(DNSConstants.MDNS_GROUP); 00448 } 00449 } 00450 if (_socket != null) { 00451 this.closeMulticastSocket(); 00452 } 00453 _socket = new MulticastSocket(DNSConstants.MDNS_PORT); 00454 if ((hostInfo != null) && (hostInfo.getInterface() != null)) { 00455 try { 00456 _socket.setNetworkInterface(hostInfo.getInterface()); 00457 } catch (SocketException e) { 00458 if (logger.isLoggable(Level.FINE)) { 00459 logger.fine("openMulticastSocket() Set network interface exception: " + e.getMessage()); 00460 } 00461 } 00462 } 00463 _socket.setTimeToLive(255); 00464 _socket.joinGroup(_group); 00465 } 00466 00467 private void closeMulticastSocket() { 00468 // jP: 20010-01-18. See below. We'll need this monitor... 00469 // assert (Thread.holdsLock(this)); 00470 if (logger.isLoggable(Level.FINER)) { 00471 logger.finer("closeMulticastSocket()"); 00472 } 00473 if (_socket != null) { 00474 // close socket 00475 try { 00476 try { 00477 _socket.leaveGroup(_group); 00478 } catch (SocketException exception) { 00479 // 00480 } 00481 _socket.close(); 00482 // jP: 20010-01-18. It isn't safe to join() on the listener 00483 // thread - it attempts to lock the IoLock object, and deadlock 00484 // ensues. Per issue #2933183, changed this to wait on the JmDNS 00485 // monitor, checking on each notify (or timeout) that the 00486 // listener thread has stopped. 00487 // 00488 while (_incomingListener != null && _incomingListener.isAlive()) { 00489 synchronized (this) { 00490 try { 00491 if (_incomingListener != null && _incomingListener.isAlive()) { 00492 // wait time is arbitrary, we're really expecting notification. 00493 if (logger.isLoggable(Level.FINER)) { 00494 logger.finer("closeMulticastSocket(): waiting for jmDNS monitor"); 00495 } 00496 this.wait(1000); 00497 } 00498 } catch (InterruptedException ignored) { 00499 // Ignored 00500 } 00501 } 00502 } 00503 _incomingListener = null; 00504 } catch (final Exception exception) { 00505 logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception); 00506 } 00507 _socket = null; 00508 } 00509 } 00510 00511 // State machine 00515 @Override 00516 public boolean advanceState(DNSTask task) { 00517 return this._localHost.advanceState(task); 00518 } 00519 00523 @Override 00524 public boolean revertState() { 00525 return this._localHost.revertState(); 00526 } 00527 00531 @Override 00532 public boolean cancelState() { 00533 return this._localHost.cancelState(); 00534 } 00535 00539 @Override 00540 public boolean closeState() { 00541 return this._localHost.closeState(); 00542 } 00543 00547 @Override 00548 public boolean recoverState() { 00549 return this._localHost.recoverState(); 00550 } 00551 00555 @Override 00556 public JmDNSImpl getDns() { 00557 return this; 00558 } 00559 00563 @Override 00564 public void associateWithTask(DNSTask task, DNSState state) { 00565 this._localHost.associateWithTask(task, state); 00566 } 00567 00571 @Override 00572 public void removeAssociationWithTask(DNSTask task) { 00573 this._localHost.removeAssociationWithTask(task); 00574 } 00575 00579 @Override 00580 public boolean isAssociatedWithTask(DNSTask task, DNSState state) { 00581 return this._localHost.isAssociatedWithTask(task, state); 00582 } 00583 00587 @Override 00588 public boolean isProbing() { 00589 return this._localHost.isProbing(); 00590 } 00591 00595 @Override 00596 public boolean isAnnouncing() { 00597 return this._localHost.isAnnouncing(); 00598 } 00599 00603 @Override 00604 public boolean isAnnounced() { 00605 return this._localHost.isAnnounced(); 00606 } 00607 00611 @Override 00612 public boolean isCanceling() { 00613 return this._localHost.isCanceling(); 00614 } 00615 00619 @Override 00620 public boolean isCanceled() { 00621 return this._localHost.isCanceled(); 00622 } 00623 00627 @Override 00628 public boolean isClosing() { 00629 return this._localHost.isClosing(); 00630 } 00631 00635 @Override 00636 public boolean isClosed() { 00637 return this._localHost.isClosed(); 00638 } 00639 00643 @Override 00644 public boolean waitForAnnounced(long timeout) { 00645 return this._localHost.waitForAnnounced(timeout); 00646 } 00647 00651 @Override 00652 public boolean waitForCanceled(long timeout) { 00653 return this._localHost.waitForCanceled(timeout); 00654 } 00655 00661 public DNSCache getCache() { 00662 return _cache; 00663 } 00664 00668 @Override 00669 public String getName() { 00670 return _name; 00671 } 00672 00676 @Override 00677 public String getHostName() { 00678 return _localHost.getName(); 00679 } 00680 00686 public HostInfo getLocalHost() { 00687 return _localHost; 00688 } 00689 00693 @Override 00694 public InetAddress getInetAddress() throws IOException { 00695 return _localHost.getInetAddress(); 00696 } 00697 00701 @Override 00702 @Deprecated 00703 public InetAddress getInterface() throws IOException { 00704 return _socket.getInterface(); 00705 } 00706 00710 @Override 00711 public ServiceInfo getServiceInfo(String type, String name) { 00712 return this.getServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); 00713 } 00714 00718 @Override 00719 public ServiceInfo getServiceInfo(String type, String name, long timeout) { 00720 return this.getServiceInfo(type, name, false, timeout); 00721 } 00722 00726 @Override 00727 public ServiceInfo getServiceInfo(String type, String name, boolean persistent) { 00728 return this.getServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); 00729 } 00730 00734 @Override 00735 public ServiceInfo getServiceInfo(String type, String name, boolean persistent, long timeout) { 00736 final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent); 00737 this.waitForInfoData(info, timeout); 00738 return (info.hasData() ? info : null); 00739 } 00740 00741 ServiceInfoImpl resolveServiceInfo(String type, String name, String subtype, boolean persistent) { 00742 this.cleanCache(); 00743 String loType = type.toLowerCase(); 00744 this.registerServiceType(type); 00745 if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) { 00746 this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS); 00747 } 00748 00749 // Check if the answer is in the cache. 00750 final ServiceInfoImpl info = this.getServiceInfoFromCache(type, name, subtype, persistent); 00751 // We still run the resolver to do the dispatch but if the info is already there it will quit immediately 00752 this.startServiceInfoResolver(info); 00753 00754 return info; 00755 } 00756 00757 ServiceInfoImpl getServiceInfoFromCache(String type, String name, String subtype, boolean persistent) { 00758 // Check if the answer is in the cache. 00759 ServiceInfoImpl info = new ServiceInfoImpl(type, name, subtype, 0, 0, 0, persistent, (byte[]) null); 00760 DNSEntry pointerEntry = this.getCache().getDNSEntry(new DNSRecord.Pointer(type, DNSRecordClass.CLASS_ANY, false, 0, info.getQualifiedName())); 00761 if (pointerEntry instanceof DNSRecord) { 00762 ServiceInfoImpl cachedInfo = (ServiceInfoImpl) ((DNSRecord) pointerEntry).getServiceInfo(persistent); 00763 if (cachedInfo != null) { 00764 // To get a complete info record we need to retrieve the service, address and the text bytes. 00765 00766 Map<Fields, String> map = cachedInfo.getQualifiedNameMap(); 00767 byte[] srvBytes = null; 00768 String server = ""; 00769 DNSEntry serviceEntry = this.getCache().getDNSEntry(info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_ANY); 00770 if (serviceEntry instanceof DNSRecord) { 00771 ServiceInfo cachedServiceEntryInfo = ((DNSRecord) serviceEntry).getServiceInfo(persistent); 00772 if (cachedServiceEntryInfo != null) { 00773 cachedInfo = new ServiceInfoImpl(map, cachedServiceEntryInfo.getPort(), cachedServiceEntryInfo.getWeight(), cachedServiceEntryInfo.getPriority(), persistent, (byte[]) null); 00774 srvBytes = cachedServiceEntryInfo.getTextBytes(); 00775 server = cachedServiceEntryInfo.getServer(); 00776 } 00777 } 00778 DNSEntry addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_A, DNSRecordClass.CLASS_ANY); 00779 if (addressEntry instanceof DNSRecord) { 00780 ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent); 00781 if (cachedAddressInfo != null) { 00782 for (Inet4Address address : cachedAddressInfo.getInet4Addresses()) { 00783 cachedInfo.addAddress(address); 00784 } 00785 cachedInfo._setText(cachedAddressInfo.getTextBytes()); 00786 } 00787 } 00788 addressEntry = this.getCache().getDNSEntry(server, DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_ANY); 00789 if (addressEntry instanceof DNSRecord) { 00790 ServiceInfo cachedAddressInfo = ((DNSRecord) addressEntry).getServiceInfo(persistent); 00791 if (cachedAddressInfo != null) { 00792 for (Inet6Address address : cachedAddressInfo.getInet6Addresses()) { 00793 cachedInfo.addAddress(address); 00794 } 00795 cachedInfo._setText(cachedAddressInfo.getTextBytes()); 00796 } 00797 } 00798 DNSEntry textEntry = this.getCache().getDNSEntry(cachedInfo.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_ANY); 00799 if (textEntry instanceof DNSRecord) { 00800 ServiceInfo cachedTextInfo = ((DNSRecord) textEntry).getServiceInfo(persistent); 00801 if (cachedTextInfo != null) { 00802 cachedInfo._setText(cachedTextInfo.getTextBytes()); 00803 } 00804 } 00805 if (cachedInfo.getTextBytes().length == 0) { 00806 cachedInfo._setText(srvBytes); 00807 } 00808 if (cachedInfo.hasData()) { 00809 info = cachedInfo; 00810 } 00811 } 00812 } 00813 return info; 00814 } 00815 00816 private void waitForInfoData(ServiceInfo info, long timeout) { 00817 synchronized (info) { 00818 long loops = (timeout / 200L); 00819 if (loops < 1) { 00820 loops = 1; 00821 } 00822 for (int i = 0; i < loops; i++) { 00823 if (info.hasData()) { 00824 break; 00825 } 00826 try { 00827 info.wait(200); 00828 } catch (final InterruptedException e) { 00829 /* Stub */ 00830 } 00831 } 00832 } 00833 } 00834 00838 @Override 00839 public void requestServiceInfo(String type, String name) { 00840 this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); 00841 } 00842 00846 @Override 00847 public void requestServiceInfo(String type, String name, boolean persistent) { 00848 this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT); 00849 } 00850 00854 @Override 00855 public void requestServiceInfo(String type, String name, long timeout) { 00856 this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT); 00857 } 00858 00862 @Override 00863 public void requestServiceInfo(String type, String name, boolean persistent, long timeout) { 00864 final ServiceInfoImpl info = this.resolveServiceInfo(type, name, "", persistent); 00865 this.waitForInfoData(info, timeout); 00866 } 00867 00868 void handleServiceResolved(ServiceEvent event) { 00869 List<ServiceListenerStatus> list = _serviceListeners.get(event.getType().toLowerCase()); 00870 final List<ServiceListenerStatus> listCopy; 00871 if ((list != null) && (!list.isEmpty())) { 00872 if ((event.getInfo() != null) && event.getInfo().hasData()) { 00873 final ServiceEvent localEvent = event; 00874 synchronized (list) { 00875 listCopy = new ArrayList<ServiceListenerStatus>(list); 00876 } 00877 for (final ServiceListenerStatus listener : listCopy) { 00878 _executor.submit(new Runnable() { 00880 @Override 00881 public void run() { 00882 listener.serviceResolved(localEvent); 00883 } 00884 }); 00885 } 00886 } 00887 } 00888 } 00889 00893 @Override 00894 public void addServiceTypeListener(ServiceTypeListener listener) throws IOException { 00895 ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); 00896 _typeListeners.add(status); 00897 00898 // report cached service types 00899 for (String type : _serviceTypes.keySet()) { 00900 status.serviceTypeAdded(new ServiceEventImpl(this, type, "", null)); 00901 } 00902 00903 this.startTypeResolver(); 00904 } 00905 00909 @Override 00910 public void removeServiceTypeListener(ServiceTypeListener listener) { 00911 ServiceTypeListenerStatus status = new ServiceTypeListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); 00912 _typeListeners.remove(status); 00913 } 00914 00918 @Override 00919 public void addServiceListener(String type, ServiceListener listener) { 00920 this.addServiceListener(type, listener, ListenerStatus.ASYNCHONEOUS); 00921 } 00922 00923 private void addServiceListener(String type, ServiceListener listener, boolean synch) { 00924 ServiceListenerStatus status = new ServiceListenerStatus(listener, synch); 00925 final String loType = type.toLowerCase(); 00926 List<ServiceListenerStatus> list = _serviceListeners.get(loType); 00927 if (list == null) { 00928 if (_serviceListeners.putIfAbsent(loType, new LinkedList<ServiceListenerStatus>()) == null) { 00929 if (_serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null) { 00930 // We have a problem here. The service collectors must be called synchronously so that their cache get cleaned up immediately or we will report . 00931 this.addServiceListener(loType, _serviceCollectors.get(loType), ListenerStatus.SYNCHONEOUS); 00932 } 00933 } 00934 list = _serviceListeners.get(loType); 00935 } 00936 if (list != null) { 00937 synchronized (list) { 00938 if (!list.contains(listener)) { 00939 list.add(status); 00940 } 00941 } 00942 } 00943 // report cached service types 00944 final List<ServiceEvent> serviceEvents = new ArrayList<ServiceEvent>(); 00945 Collection<DNSEntry> dnsEntryLits = this.getCache().allValues(); 00946 for (DNSEntry entry : dnsEntryLits) { 00947 final DNSRecord record = (DNSRecord) entry; 00948 if (record.getRecordType() == DNSRecordType.TYPE_SRV) { 00949 if (record.getKey().endsWith(loType)) { 00950 // Do not used the record embedded method for generating event this will not work. 00951 // serviceEvents.add(record.getServiceEvent(this)); 00952 serviceEvents.add(new ServiceEventImpl(this, record.getType(), toUnqualifiedName(record.getType(), record.getName()), record.getServiceInfo())); 00953 } 00954 } 00955 } 00956 // Actually call listener with all service events added above 00957 for (ServiceEvent serviceEvent : serviceEvents) { 00958 status.serviceAdded(serviceEvent); 00959 } 00960 // Create/start ServiceResolver 00961 this.startServiceResolver(type); 00962 } 00963 00967 @Override 00968 public void removeServiceListener(String type, ServiceListener listener) { 00969 String loType = type.toLowerCase(); 00970 List<ServiceListenerStatus> list = _serviceListeners.get(loType); 00971 // DJS Trigger callbacks for service infos 00972 ServiceCollector collector = _serviceCollectors.get(type); 00973 for ( ServiceInfo info : Arrays.asList(collector.list(-1)) ) { 00974 final ServiceEvent event = new ServiceEventImpl(this,info.getType(), info.getName(), info); 00975 final List<ServiceListenerStatus> listCopy; 00976 if ((list != null) && (!list.isEmpty())) { 00977 synchronized (list) { 00978 listCopy = new ArrayList<ServiceListenerStatus>(list); 00979 } 00980 for (final ServiceListenerStatus listener_status : listCopy) { 00981 _executor.submit(new Runnable() { 00983 @Override 00984 public void run() { 00985 listener_status.serviceRemoved(event); 00986 } 00987 }); 00988 } 00989 } 00990 } 00991 // resume normal service 00992 if (list != null) { 00993 synchronized (list) { 00994 ServiceListenerStatus status = new ServiceListenerStatus(listener, ListenerStatus.ASYNCHONEOUS); 00995 list.remove(status); 00996 if (list.isEmpty()) { 00997 _serviceListeners.remove(loType, list); 00998 } 00999 } 01000 } 01001 } 01002 01006 @Override 01007 public void registerService(ServiceInfo infoAbstract) throws IOException { 01008 if (this.isClosing() || this.isClosed()) { 01009 throw new IllegalStateException("This DNS is closed."); 01010 } 01011 final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; 01012 01013 if (info.getDns() != null) { 01014 if (info.getDns() != this) { 01015 throw new IllegalStateException("A service information can only be registered with a single instamce of JmDNS."); 01016 } else if (_services.get(info.getKey()) != null) { 01017 throw new IllegalStateException("A service information can only be registered once."); 01018 } 01019 } 01020 info.setDns(this); 01021 01022 this.registerServiceType(info.getTypeWithSubtype()); 01023 01024 // bind the service to this address 01025 info.recoverState(); 01026 info.setServer(_localHost.getName()); 01027 info.addAddress(_localHost.getInet4Address()); 01028 info.addAddress(_localHost.getInet6Address()); 01029 01030 this.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT); 01031 01032 this.makeServiceNameUnique(info); 01033 while (_services.putIfAbsent(info.getKey(), info) != null) { 01034 this.makeServiceNameUnique(info); 01035 } 01036 01037 this.startProber(); 01038 info.waitForAnnounced(DNSConstants.SERVICE_INFO_TIMEOUT); 01039 01040 if (logger.isLoggable(Level.FINE)) { 01041 logger.fine("registerService() JmDNS registered service as " + info); 01042 } 01043 } 01044 01048 @Override 01049 public void unregisterService(ServiceInfo infoAbstract) { 01050 final ServiceInfoImpl info = (ServiceInfoImpl) _services.get(infoAbstract.getKey()); 01051 01052 if (info != null) { 01053 info.cancelState(); 01054 this.startCanceler(); 01055 info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 01056 01057 _services.remove(info.getKey(), info); 01058 if (logger.isLoggable(Level.FINE)) { 01059 logger.fine("unregisterService() JmDNS unregistered service as " + info); 01060 } 01061 } else { 01062 logger.warning("Removing unregistered service info: " + infoAbstract.getKey()); 01063 } 01064 } 01065 01069 @Override 01070 public void unregisterAllServices() { 01071 if (logger.isLoggable(Level.FINER)) { 01072 logger.finer("unregisterAllServices()"); 01073 } 01074 01075 for (String name : _services.keySet()) { 01076 ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name); 01077 if (info != null) { 01078 if (logger.isLoggable(Level.FINER)) { 01079 logger.finer("Cancelling service info: " + info); 01080 } 01081 info.cancelState(); 01082 } 01083 } 01084 this.startCanceler(); 01085 01086 for (String name : _services.keySet()) { 01087 ServiceInfoImpl info = (ServiceInfoImpl) _services.get(name); 01088 if (info != null) { 01089 if (logger.isLoggable(Level.FINER)) { 01090 logger.finer("Wait for service info cancel: " + info); 01091 } 01092 info.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 01093 _services.remove(name, info); 01094 } 01095 } 01096 01097 } 01098 01102 @Override 01103 public boolean registerServiceType(String type) { 01104 boolean typeAdded = false; 01105 Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(type); 01106 String domain = map.get(Fields.Domain); 01107 String protocol = map.get(Fields.Protocol); 01108 String application = map.get(Fields.Application); 01109 String subtype = map.get(Fields.Subtype); 01110 01111 final String name = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 01112 final String loname = name.toLowerCase(); 01113 if (logger.isLoggable(Level.FINE)) { 01114 logger.fine(this.getName() + ".registering service type: " + type + " as: " + name + (subtype.length() > 0 ? " subtype: " + subtype : "")); 01115 } 01116 if (!_serviceTypes.containsKey(loname) && !application.toLowerCase().equals("dns-sd") && !domain.toLowerCase().endsWith("in-addr.arpa") && !domain.toLowerCase().endsWith("ip6.arpa")) { 01117 typeAdded = _serviceTypes.putIfAbsent(loname, new ServiceTypeEntry(name)) == null; 01118 if (typeAdded) { 01119 final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]); 01120 final ServiceEvent event = new ServiceEventImpl(this, name, "", null); 01121 for (final ServiceTypeListenerStatus status : list) { 01122 _executor.submit(new Runnable() { 01124 @Override 01125 public void run() { 01126 status.serviceTypeAdded(event); 01127 } 01128 }); 01129 } 01130 } 01131 } 01132 if (subtype.length() > 0) { 01133 ServiceTypeEntry subtypes = _serviceTypes.get(loname); 01134 if ((subtypes != null) && (!subtypes.contains(subtype))) { 01135 synchronized (subtypes) { 01136 if (!subtypes.contains(subtype)) { 01137 typeAdded = true; 01138 subtypes.add(subtype); 01139 final ServiceTypeListenerStatus[] list = _typeListeners.toArray(new ServiceTypeListenerStatus[_typeListeners.size()]); 01140 final ServiceEvent event = new ServiceEventImpl(this, "_" + subtype + "._sub." + name, "", null); 01141 for (final ServiceTypeListenerStatus status : list) { 01142 _executor.submit(new Runnable() { 01144 @Override 01145 public void run() { 01146 status.subTypeForServiceTypeAdded(event); 01147 } 01148 }); 01149 } 01150 } 01151 } 01152 } 01153 } 01154 return typeAdded; 01155 } 01156 01162 private boolean makeServiceNameUnique(ServiceInfoImpl info) { 01163 final String originalQualifiedName = info.getKey(); 01164 final long now = System.currentTimeMillis(); 01165 01166 boolean collision; 01167 do { 01168 collision = false; 01169 01170 // Check for collision in cache 01171 for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(info.getKey())) { 01172 if (DNSRecordType.TYPE_SRV.equals(dnsEntry.getRecordType()) && !dnsEntry.isExpired(now)) { 01173 final DNSRecord.Service s = (DNSRecord.Service) dnsEntry; 01174 if (s.getPort() != info.getPort() || !s.getServer().equals(_localHost.getName())) { 01175 if (logger.isLoggable(Level.FINER)) { 01176 logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + dnsEntry + " s.server=" + s.getServer() + " " + _localHost.getName() + " equals:" + (s.getServer().equals(_localHost.getName()))); 01177 } 01178 info.setName(incrementName(info.getName())); 01179 collision = true; 01180 break; 01181 } 01182 } 01183 } 01184 01185 // Check for collision with other service infos published by JmDNS 01186 final ServiceInfo selfService = _services.get(info.getKey()); 01187 if (selfService != null && selfService != info) { 01188 info.setName(incrementName(info.getName())); 01189 collision = true; 01190 } 01191 } 01192 while (collision); 01193 01194 return !(originalQualifiedName.equals(info.getKey())); 01195 } 01196 01197 String incrementName(String name) { 01198 String aName = name; 01199 try { 01200 final int l = aName.lastIndexOf('('); 01201 final int r = aName.lastIndexOf(')'); 01202 if ((l >= 0) && (l < r)) { 01203 aName = aName.substring(0, l) + "(" + (Integer.parseInt(aName.substring(l + 1, r)) + 1) + ")"; 01204 } else { 01205 aName += " (2)"; 01206 } 01207 } catch (final NumberFormatException e) { 01208 aName += " (2)"; 01209 } 01210 return aName; 01211 } 01212 01221 public void addListener(DNSListener listener, DNSQuestion question) { 01222 final long now = System.currentTimeMillis(); 01223 01224 // add the new listener 01225 _listeners.add(listener); 01226 01227 // report existing matched records 01228 01229 if (question != null) { 01230 for (DNSEntry dnsEntry : this.getCache().getDNSEntryList(question.getName().toLowerCase())) { 01231 if (question.answeredBy(dnsEntry) && !dnsEntry.isExpired(now)) { 01232 listener.updateRecord(this.getCache(), now, dnsEntry); 01233 } 01234 } 01235 } 01236 } 01237 01244 public void removeListener(DNSListener listener) { 01245 _listeners.remove(listener); 01246 } 01247 01254 public void renewServiceCollector(DNSRecord record) { 01255 ServiceInfo info = record.getServiceInfo(); 01256 if (_serviceCollectors.containsKey(info.getType().toLowerCase())) { 01257 // Create/start ServiceResolver 01258 this.startServiceResolver(info.getType()); 01259 } 01260 } 01261 01262 // Remind: Method updateRecord should receive a better name. 01273 public void updateRecord(long now, DNSRecord rec, Operation operation) { 01274 // We do not want to block the entire DNS while we are updating the record for each listener (service info) 01275 { 01276 List<DNSListener> listenerList = null; 01277 synchronized (_listeners) { 01278 listenerList = new ArrayList<DNSListener>(_listeners); 01279 } 01280 for (DNSListener listener : listenerList) { 01281 listener.updateRecord(this.getCache(), now, rec); 01282 } 01283 } 01284 if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType())) 01285 // if (DNSRecordType.TYPE_PTR.equals(rec.getRecordType()) || DNSRecordType.TYPE_SRV.equals(rec.getRecordType())) 01286 { 01287 ServiceEvent event = rec.getServiceEvent(this); 01288 if ((event.getInfo() == null) || !event.getInfo().hasData()) { 01289 // We do not care about the subtype because the info is only used if complete and the subtype will then be included. 01290 ServiceInfo info = this.getServiceInfoFromCache(event.getType(), event.getName(), "", false); 01291 if (info.hasData()) { 01292 event = new ServiceEventImpl(this, event.getType(), event.getName(), info); 01293 } 01294 } 01295 01296 List<ServiceListenerStatus> list = _serviceListeners.get(event.getType().toLowerCase()); 01297 final List<ServiceListenerStatus> serviceListenerList; 01298 if (list != null) { 01299 synchronized (list) { 01300 serviceListenerList = new ArrayList<ServiceListenerStatus>(list); 01301 } 01302 } else { 01303 serviceListenerList = Collections.emptyList(); 01304 } 01305 if (logger.isLoggable(Level.FINEST)) { 01306 logger.finest(this.getName() + ".updating record for event: " + event + " list " + serviceListenerList + " operation: " + operation); 01307 } 01308 //System.out.println("Jmdns::updateRecord " + this.getName() + " updating record for event: " + event + "\n list " + serviceListenerList + "\n operation: " + operation ); 01309 if (!serviceListenerList.isEmpty()) { 01310 final ServiceEvent localEvent = event; 01311 01312 switch (operation) { 01313 case Add: 01314 for (final ServiceListenerStatus listener : serviceListenerList) { 01315 if (listener.isSynchronous()) { 01316 listener.serviceAdded(localEvent); 01317 } else { 01318 _executor.submit(new Runnable() { 01320 @Override 01321 public void run() { 01322 listener.serviceAdded(localEvent); 01323 } 01324 }); 01325 } 01326 } 01327 break; 01328 case Remove: 01329 for (final ServiceListenerStatus listener : serviceListenerList) { 01330 if (listener.isSynchronous()) { 01331 listener.serviceRemoved(localEvent); 01332 } else { 01333 _executor.submit(new Runnable() { 01335 @Override 01336 public void run() { 01337 listener.serviceRemoved(localEvent); 01338 } 01339 }); 01340 } 01341 } 01342 break; 01343 default: 01344 break; 01345 } 01346 } 01347 } 01348 } 01349 01350 void handleRecord(DNSRecord record, long now) { 01351 DNSRecord newRecord = record; 01352 01353 Operation cacheOperation = Operation.Noop; 01354 final boolean expired = newRecord.isExpired(now); 01355 if (logger.isLoggable(Level.FINE)) { 01356 logger.fine(this.getName() + " handle response: " + newRecord); 01357 } 01358 01359 // update the cache 01360 if (!newRecord.isServicesDiscoveryMetaQuery() && !newRecord.isDomainDiscoveryQuery()) { 01361 final boolean unique = newRecord.isUnique(); 01362 final DNSRecord cachedRecord = (DNSRecord) this.getCache().getDNSEntry(newRecord); 01363 if (logger.isLoggable(Level.FINE)) { 01364 logger.fine(this.getName() + " handle response cached record: " + cachedRecord); 01365 } 01366 if (unique) { 01367 for (DNSEntry entry : this.getCache().getDNSEntryList(newRecord.getKey())) { 01368 if (newRecord.getRecordType().equals(entry.getRecordType()) && newRecord.getRecordClass().equals(entry.getRecordClass()) && (entry != cachedRecord)) { 01369 ((DNSRecord) entry).setWillExpireSoon(now); 01370 } 01371 } 01372 } 01373 if (cachedRecord != null) { 01374 if (expired) { 01375 // if the record has a 0 ttl that means we have a cancel record we need to delay the removal by 1s 01376 if (newRecord.getTTL() == 0) { 01377 cacheOperation = Operation.Noop; 01378 cachedRecord.setWillExpireSoon(now); 01379 // the actual record will be disposed of by the record reaper. 01380 } else { 01381 cacheOperation = Operation.Remove; 01382 this.getCache().removeDNSEntry(cachedRecord); 01383 } 01384 } else { 01385 // If the record content has changed we need to inform our listeners. 01386 if (!newRecord.sameValue(cachedRecord) || (!newRecord.sameSubtype(cachedRecord) && (newRecord.getSubtype().length() > 0))) { 01387 if (newRecord.isSingleValued()) { 01388 cacheOperation = Operation.Update; 01389 this.getCache().replaceDNSEntry(newRecord, cachedRecord); 01390 } else { 01391 // Address record can have more than one value on multi-homed machines 01392 cacheOperation = Operation.Add; 01393 this.getCache().addDNSEntry(newRecord); 01394 } 01395 } else { 01396 cachedRecord.resetTTL(newRecord); 01397 newRecord = cachedRecord; 01398 } 01399 } 01400 } else { 01401 if (!expired) { 01402 cacheOperation = Operation.Add; 01403 this.getCache().addDNSEntry(newRecord); 01404 } 01405 } 01406 } 01407 01408 // Register new service types 01409 if (newRecord.getRecordType() == DNSRecordType.TYPE_PTR) { 01410 // handle DNSConstants.DNS_META_QUERY records 01411 boolean typeAdded = false; 01412 if (newRecord.isServicesDiscoveryMetaQuery()) { 01413 // The service names are in the alias. 01414 if (!expired) { 01415 typeAdded = this.registerServiceType(((DNSRecord.Pointer) newRecord).getAlias()); 01416 } 01417 return; 01418 } 01419 typeAdded |= this.registerServiceType(newRecord.getName()); 01420 if (typeAdded && (cacheOperation == Operation.Noop)) { 01421 cacheOperation = Operation.RegisterServiceType; 01422 } 01423 } 01424 01425 // notify the listeners 01426 if (cacheOperation != Operation.Noop) { 01427 this.updateRecord(now, newRecord, cacheOperation); 01428 } 01429 01430 } 01431 01437 void handleResponse(DNSIncoming msg) throws IOException { 01438 final long now = System.currentTimeMillis(); 01439 01440 boolean hostConflictDetected = false; 01441 boolean serviceConflictDetected = false; 01442 String addr = getLocalHost().getInetAddress().getHostAddress(); 01443 for (DNSRecord newRecord : msg.getAllAnswers()) { 01444 String new_record = newRecord.toString(); 01445 // if ( ( !new_record.contains("TYPE_PTR") ) && ( !new_record.contains("TYPE_TXT") ) ) { 01446 // System.out.printf("[%s] %s\n",addr,new_record); 01447 // } 01448 this.handleRecord(newRecord, now); 01449 01450 if (DNSRecordType.TYPE_A.equals(newRecord.getRecordType()) || DNSRecordType.TYPE_AAAA.equals(newRecord.getRecordType())) { 01451 hostConflictDetected |= newRecord.handleResponse(this); 01452 } else { 01453 serviceConflictDetected |= newRecord.handleResponse(this); 01454 } 01455 } 01456 if (hostConflictDetected || serviceConflictDetected) { 01457 this.startProber(); 01458 } 01459 } 01460 01469 void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException { 01470 if (logger.isLoggable(Level.FINE)) { 01471 logger.fine(this.getName() + ".handle query: " + in); 01472 } 01473 // Track known answers 01474 boolean conflictDetected = false; 01475 final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL; 01476 for (DNSRecord answer : in.getAllAnswers()) { 01477 conflictDetected |= answer.handleQuery(this, expirationTime); 01478 } 01479 01480 this.ioLock(); 01481 try { 01482 01483 if (_plannedAnswer != null) { 01484 _plannedAnswer.append(in); 01485 } else { 01486 DNSIncoming plannedAnswer = in.clone(); 01487 if (in.isTruncated()) { 01488 _plannedAnswer = plannedAnswer; 01489 } 01490 this.startResponder(plannedAnswer, port); 01491 } 01492 01493 } finally { 01494 this.ioUnlock(); 01495 } 01496 01497 final long now = System.currentTimeMillis(); 01498 for (DNSRecord answer : in.getAnswers()) { 01499 this.handleRecord(answer, now); 01500 } 01501 01502 if (conflictDetected) { 01503 this.startProber(); 01504 } 01505 } 01506 01507 public void respondToQuery(DNSIncoming in) { 01508 this.ioLock(); 01509 try { 01510 if (_plannedAnswer == in) { 01511 _plannedAnswer = null; 01512 } 01513 } finally { 01514 this.ioUnlock(); 01515 } 01516 } 01517 01529 public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException { 01530 DNSOutgoing newOut = out; 01531 if (newOut == null) { 01532 newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload()); 01533 } 01534 try { 01535 newOut.addAnswer(in, rec); 01536 } catch (final IOException e) { 01537 newOut.setFlags(newOut.getFlags() | DNSConstants.FLAGS_TC); 01538 newOut.setId(in.getId()); 01539 send(newOut); 01540 01541 newOut = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false, in.getSenderUDPPayload()); 01542 newOut.addAnswer(in, rec); 01543 } 01544 return newOut; 01545 } 01546 01553 public void send(DNSOutgoing out) throws IOException { 01554 if (!out.isEmpty()) { 01555 byte[] message = out.data(); 01556 final DatagramPacket packet = new DatagramPacket(message, message.length, _group, DNSConstants.MDNS_PORT); 01557 01558 if (logger.isLoggable(Level.FINEST)) { 01559 try { 01560 final DNSIncoming msg = new DNSIncoming(packet); 01561 if (logger.isLoggable(Level.FINEST)) { 01562 logger.finest("send(" + this.getName() + ") JmDNS out:" + msg.print(true)); 01563 } 01564 } catch (final IOException e) { 01565 logger.throwing(getClass().toString(), "send(" + this.getName() + ") - JmDNS can not parse what it sends!!!", e); 01566 } 01567 } 01568 final MulticastSocket ms = _socket; 01569 if (ms != null && !ms.isClosed()) { 01570 ms.send(packet); 01571 } 01572 } 01573 } 01574 01575 /* 01576 * (non-Javadoc) 01577 * @see javax.jmdns.impl.DNSTaskStarter#purgeTimer() 01578 */ 01579 @Override 01580 public void purgeTimer() { 01581 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeTimer(); 01582 } 01583 01584 /* 01585 * (non-Javadoc) 01586 * @see javax.jmdns.impl.DNSTaskStarter#purgeStateTimer() 01587 */ 01588 @Override 01589 public void purgeStateTimer() { 01590 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeStateTimer(); 01591 } 01592 01593 /* 01594 * (non-Javadoc) 01595 * @see javax.jmdns.impl.DNSTaskStarter#cancelTimer() 01596 */ 01597 @Override 01598 public void cancelTimer() { 01599 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelTimer(); 01600 } 01601 01602 /* 01603 * (non-Javadoc) 01604 * @see javax.jmdns.impl.DNSTaskStarter#cancelStateTimer() 01605 */ 01606 @Override 01607 public void cancelStateTimer() { 01608 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelStateTimer(); 01609 } 01610 01611 /* 01612 * (non-Javadoc) 01613 * @see javax.jmdns.impl.DNSTaskStarter#startProber() 01614 */ 01615 @Override 01616 public void startProber() { 01617 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startProber(); 01618 } 01619 01620 /* 01621 * (non-Javadoc) 01622 * @see javax.jmdns.impl.DNSTaskStarter#startAnnouncer() 01623 */ 01624 @Override 01625 public void startAnnouncer() { 01626 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startAnnouncer(); 01627 } 01628 01629 /* 01630 * (non-Javadoc) 01631 * @see javax.jmdns.impl.DNSTaskStarter#startRenewer() 01632 */ 01633 @Override 01634 public void startRenewer() { 01635 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startRenewer(); 01636 } 01637 01638 /* 01639 * (non-Javadoc) 01640 * @see javax.jmdns.impl.DNSTaskStarter#startCanceler() 01641 */ 01642 @Override 01643 public void startCanceler() { 01644 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startCanceler(); 01645 } 01646 01647 /* 01648 * (non-Javadoc) 01649 * @see javax.jmdns.impl.DNSTaskStarter#startReaper() 01650 */ 01651 @Override 01652 public void startReaper() { 01653 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startReaper(); 01654 } 01655 01656 /* 01657 * (non-Javadoc) 01658 * @see javax.jmdns.impl.DNSTaskStarter#startServiceInfoResolver(javax.jmdns.impl.ServiceInfoImpl) 01659 */ 01660 @Override 01661 public void startServiceInfoResolver(ServiceInfoImpl info) { 01662 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceInfoResolver(info); 01663 } 01664 01665 /* 01666 * (non-Javadoc) 01667 * @see javax.jmdns.impl.DNSTaskStarter#startTypeResolver() 01668 */ 01669 @Override 01670 public void startTypeResolver() { 01671 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startTypeResolver(); 01672 } 01673 01674 /* 01675 * (non-Javadoc) 01676 * @see javax.jmdns.impl.DNSTaskStarter#startServiceResolver(java.lang.String) 01677 */ 01678 @Override 01679 public void startServiceResolver(String type) { 01680 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceResolver(type); 01681 } 01682 01683 /* 01684 * (non-Javadoc) 01685 * @see javax.jmdns.impl.DNSTaskStarter#startResponder(javax.jmdns.impl.DNSIncoming, int) 01686 */ 01687 @Override 01688 public void startResponder(DNSIncoming in, int port) { 01689 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startResponder(in, port); 01690 } 01691 01692 // REMIND: Why is this not an anonymous inner class? 01696 protected class Shutdown implements Runnable { 01698 @Override 01699 public void run() { 01700 try { 01701 _shutdown = null; 01702 close(); 01703 } catch (Throwable exception) { 01704 System.err.println("Error while shuting down. " + exception); 01705 } 01706 } 01707 } 01708 01709 private final Object _recoverLock = new Object(); 01710 01714 public void recover() { 01715 logger.finer(this.getName() + "recover()"); 01716 // We have an IO error so lets try to recover if anything happens lets close it. 01717 // This should cover the case of the IP address changing under our feet 01718 if (this.isClosing() || this.isClosed() || this.isCanceling() || this.isCanceled()) { 01719 return; 01720 } 01721 01722 // 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 01723 // 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. 01724 synchronized (_recoverLock) { 01725 // Stop JmDNS 01726 // This protects against recursive calls 01727 if (this.cancelState()) { 01728 logger.finer(this.getName() + "recover() thread " + Thread.currentThread().getName()); 01729 Thread recover = new Thread(this.getName() + ".recover()") { 01733 @Override 01734 public void run() { 01735 __recover(); 01736 } 01737 }; 01738 recover.start(); 01739 } 01740 } 01741 } 01742 01743 void __recover() { 01744 // Synchronize only if we are not already in process to prevent dead locks 01745 // 01746 if (logger.isLoggable(Level.FINER)) { 01747 logger.finer(this.getName() + "recover() Cleanning up"); 01748 } 01749 01750 logger.warning("RECOVERING"); 01751 // Purge the timer 01752 this.purgeTimer(); 01753 01754 // We need to keep a copy for reregistration 01755 final Collection<ServiceInfo> oldServiceInfos = new ArrayList<ServiceInfo>(getServices().values()); 01756 01757 // Cancel all services 01758 this.unregisterAllServices(); 01759 this.disposeServiceCollectors(); 01760 01761 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 01762 01763 // Purge the canceler timer 01764 this.purgeStateTimer(); 01765 01766 // 01767 // close multicast socket 01768 this.closeMulticastSocket(); 01769 01770 // 01771 this.getCache().clear(); 01772 if (logger.isLoggable(Level.FINER)) { 01773 logger.finer(this.getName() + "recover() All is clean"); 01774 } 01775 01776 if (this.isCanceled()) { 01777 // 01778 // All is clear now start the services 01779 // 01780 for (ServiceInfo info : oldServiceInfos) { 01781 ((ServiceInfoImpl) info).recoverState(); 01782 } 01783 this.recoverState(); 01784 01785 try { 01786 this.openMulticastSocket(this.getLocalHost()); 01787 this.start(oldServiceInfos); 01788 } catch (final Exception exception) { 01789 logger.log(Level.WARNING, this.getName() + "recover() Start services exception ", exception); 01790 } 01791 logger.log(Level.WARNING, this.getName() + "recover() We are back!"); 01792 } else { 01793 // We have a problem. We could not clear the state. 01794 logger.log(Level.WARNING, this.getName() + "recover() Could not recover we are Down!"); 01795 if (this.getDelegate() != null) { 01796 this.getDelegate().cannotRecoverFromIOError(this.getDns(), oldServiceInfos); 01797 } 01798 } 01799 01800 } 01801 01802 public void cleanCache() { 01803 long now = System.currentTimeMillis(); 01804 for (DNSEntry entry : this.getCache().allValues()) { 01805 try { 01806 DNSRecord record = (DNSRecord) entry; 01807 if (record.isExpired(now)) { 01808 this.updateRecord(now, record, Operation.Remove); 01809 this.getCache().removeDNSEntry(record); 01810 } else if (record.isStale(now)) { 01811 // we should query for the record we care about i.e. those in the service collectors 01812 this.renewServiceCollector(record); 01813 } 01814 } catch (Exception exception) { 01815 logger.log(Level.SEVERE, this.getName() + ".Error while reaping records: " + entry, exception); 01816 logger.severe(this.toString()); 01817 } 01818 } 01819 } 01820 01824 @Override 01825 public void close() { 01826 if (this.isClosing()) { 01827 return; 01828 } 01829 01830 if (logger.isLoggable(Level.FINER)) { 01831 logger.finer("Cancelling JmDNS: " + this); 01832 } 01833 // Stop JmDNS 01834 // This protects against recursive calls 01835 if (this.closeState()) { 01836 // We got the tie break now clean up 01837 01838 // Stop the timer 01839 logger.finer("Canceling the timer"); 01840 this.cancelTimer(); 01841 01842 // Cancel all services 01843 this.unregisterAllServices(); 01844 this.disposeServiceCollectors(); 01845 01846 if (logger.isLoggable(Level.FINER)) { 01847 logger.finer("Wait for JmDNS cancel: " + this); 01848 } 01849 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); 01850 01851 // Stop the canceler timer 01852 logger.finer("Canceling the state timer"); 01853 this.cancelStateTimer(); 01854 01855 // Stop the executor 01856 _executor.shutdown(); 01857 01858 // close socket 01859 this.closeMulticastSocket(); 01860 01861 // remove the shutdown hook 01862 if (_shutdown != null) { 01863 Runtime.getRuntime().removeShutdownHook(_shutdown); 01864 } 01865 01866 if (logger.isLoggable(Level.FINER)) { 01867 logger.finer("JmDNS closed."); 01868 } 01869 } 01870 advanceState(null); 01871 } 01872 01876 @Override 01877 @Deprecated 01878 public void printServices() { 01879 System.err.println(toString()); 01880 } 01881 01885 @Override 01886 public String toString() { 01887 final StringBuilder aLog = new StringBuilder(2048); 01888 aLog.append("\t---- Local Host -----"); 01889 aLog.append("\n\t"); 01890 aLog.append(_localHost); 01891 aLog.append("\n\t---- Services -----"); 01892 for (String key : _services.keySet()) { 01893 aLog.append("\n\t\tService: "); 01894 aLog.append(key); 01895 aLog.append(": "); 01896 aLog.append(_services.get(key)); 01897 } 01898 aLog.append("\n"); 01899 aLog.append("\t---- Types ----"); 01900 for (String key : _serviceTypes.keySet()) { 01901 ServiceTypeEntry subtypes = _serviceTypes.get(key); 01902 aLog.append("\n\t\tType: "); 01903 aLog.append(subtypes.getType()); 01904 aLog.append(": "); 01905 aLog.append(subtypes.isEmpty() ? "no subtypes" : subtypes); 01906 } 01907 aLog.append("\n"); 01908 aLog.append(_cache.toString()); 01909 aLog.append("\n"); 01910 aLog.append("\t---- Service Collectors ----"); 01911 for (String key : _serviceCollectors.keySet()) { 01912 aLog.append("\n\t\tService Collector: "); 01913 aLog.append(key); 01914 aLog.append(": "); 01915 aLog.append(_serviceCollectors.get(key)); 01916 } 01917 aLog.append("\n"); 01918 aLog.append("\t---- Service Listeners ----"); 01919 for (String key : _serviceListeners.keySet()) { 01920 aLog.append("\n\t\tService Listener: "); 01921 aLog.append(key); 01922 aLog.append(": "); 01923 aLog.append(_serviceListeners.get(key)); 01924 } 01925 return aLog.toString(); 01926 } 01927 01931 @Override 01932 public ServiceInfo[] list(String type) { 01933 return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT); 01934 } 01935 01939 @Override 01940 public ServiceInfo[] list(String type, long timeout) { 01941 this.cleanCache(); 01942 // Implementation note: The first time a list for a given type is 01943 // requested, a ServiceCollector is created which collects service 01944 // infos. This greatly speeds up the performance of subsequent calls 01945 // to this method. The caveats are, that 1) the first call to this 01946 // method for a given type is slow, and 2) we spawn a ServiceCollector 01947 // instance for each service type which increases network traffic a 01948 // little. 01949 01950 String loType = type.toLowerCase(); 01951 01952 boolean newCollectorCreated = false; 01953 if (this.isCanceling() || this.isCanceled()) { 01954 return new ServiceInfo[0]; 01955 } 01956 01957 ServiceCollector collector = _serviceCollectors.get(loType); 01958 if (collector == null) { 01959 newCollectorCreated = _serviceCollectors.putIfAbsent(loType, new ServiceCollector(type)) == null; 01960 collector = _serviceCollectors.get(loType); 01961 if (newCollectorCreated) { 01962 this.addServiceListener(type, collector, ListenerStatus.SYNCHONEOUS); 01963 } 01964 } 01965 if (logger.isLoggable(Level.FINER)) { 01966 logger.finer(this.getName() + ".collector: " + collector); 01967 } 01968 // DJS: let's see what the collector finds for us. 01969 // try { 01970 // System.out.println("Jmdns::list() : " + this.getInetAddress().getHostAddress()); 01971 // for ( ServiceInfo service_info : collector.list(timeout) ) { 01972 // System.out.println(" Name : " + service_info.getQualifiedName()); 01973 // System.out.println(" Port : " + service_info.getPort()); 01974 // System.out.println(" Hostname: " + service_info.getServer()); 01975 // for ( int i = 0; i < service_info.getInetAddresses().length; ++i ) { 01976 // System.out.println(" Address : " + service_info.getInetAddresses()[i].getHostAddress()); 01977 // } 01978 // } 01979 // } catch (IOException e) {} 01980 01981 // At this stage the collector should never be null but it keeps findbugs happy. 01982 return (collector != null ? collector.list(timeout) : new ServiceInfo[0]); 01983 } 01984 01988 @Override 01989 public Map<String, ServiceInfo[]> listBySubtype(String type) { 01990 return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT); 01991 } 01992 01996 @Override 01997 public Map<String, ServiceInfo[]> listBySubtype(String type, long timeout) { 01998 Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5); 01999 for (ServiceInfo info : this.list(type, timeout)) { 02000 String subtype = info.getSubtype().toLowerCase(); 02001 if (!map.containsKey(subtype)) { 02002 map.put(subtype, new ArrayList<ServiceInfo>(10)); 02003 } 02004 map.get(subtype).add(info); 02005 } 02006 02007 Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size()); 02008 for (String subtype : map.keySet()) { 02009 List<ServiceInfo> infoForSubType = map.get(subtype); 02010 result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()])); 02011 } 02012 02013 return result; 02014 } 02015 02022 private void disposeServiceCollectors() { 02023 if (logger.isLoggable(Level.FINER)) { 02024 logger.finer("disposeServiceCollectors()"); 02025 } 02026 for (String type : _serviceCollectors.keySet()) { 02027 ServiceCollector collector = _serviceCollectors.get(type); 02028 if (collector != null) { 02029 this.removeServiceListener(type, collector); 02030 _serviceCollectors.remove(type, collector); 02031 } 02032 } 02033 } 02034 02040 private static class ServiceCollector implements ServiceListener { 02041 // private static Logger logger = Logger.getLogger(ServiceCollector.class.getName()); 02042 02046 private final ConcurrentMap<String, ServiceInfo> _infos; 02047 02051 private final ConcurrentMap<String, ServiceEvent> _events; 02052 02056 private final String _type; 02057 02061 private volatile boolean _needToWaitForInfos; 02062 02063 public ServiceCollector(String type) { 02064 super(); 02065 _infos = new ConcurrentHashMap<String, ServiceInfo>(); 02066 _events = new ConcurrentHashMap<String, ServiceEvent>(); 02067 _type = type; 02068 _needToWaitForInfos = true; 02069 } 02070 02077 @Override 02078 public void serviceAdded(ServiceEvent event) { 02079 synchronized (this) { 02080 ServiceInfo info = event.getInfo(); 02081 if ((info != null) && (info.hasData())) { 02082 _infos.put(event.getName(), info); 02083 } else { 02084 String subtype = (info != null ? info.getSubtype() : ""); 02085 info = ((JmDNSImpl) event.getDNS()).resolveServiceInfo(event.getType(), event.getName(), subtype, true); 02086 if (info != null) { 02087 _infos.put(event.getName(), info); 02088 } else { 02089 _events.put(event.getName(), event); 02090 } 02091 } 02092 } 02093 } 02094 02101 @Override 02102 public void serviceRemoved(ServiceEvent event) { 02103 synchronized (this) { 02104 _infos.remove(event.getName()); 02105 _events.remove(event.getName()); 02106 } 02107 } 02108 02115 @Override 02116 public void serviceResolved(ServiceEvent event) { 02117 synchronized (this) { 02118 _infos.put(event.getName(), event.getInfo()); 02119 _events.remove(event.getName()); 02120 } 02121 } 02122 02130 public ServiceInfo[] list(long timeout) { 02131 if (_infos.isEmpty() || !_events.isEmpty() || _needToWaitForInfos) { 02132 long loops = (timeout / 200L); 02133 if (loops < 1) { 02134 loops = 1; 02135 } 02136 for (int i = 0; i < loops; i++) { 02137 try { 02138 Thread.sleep(200); 02139 } catch (final InterruptedException e) { 02140 /* Stub */ 02141 } 02142 if (_events.isEmpty() && !_infos.isEmpty() && !_needToWaitForInfos) { 02143 break; 02144 } 02145 } 02146 } 02147 _needToWaitForInfos = false; 02148 return _infos.values().toArray(new ServiceInfo[_infos.size()]); 02149 } 02150 02154 @Override 02155 public String toString() { 02156 final StringBuffer aLog = new StringBuffer(); 02157 aLog.append("\n\tType: "); 02158 aLog.append(_type); 02159 if (_infos.isEmpty()) { 02160 aLog.append("\n\tNo services collected."); 02161 } else { 02162 aLog.append("\n\tServices"); 02163 for (String key : _infos.keySet()) { 02164 aLog.append("\n\t\tService: "); 02165 aLog.append(key); 02166 aLog.append(": "); 02167 aLog.append(_infos.get(key)); 02168 } 02169 } 02170 if (_events.isEmpty()) { 02171 aLog.append("\n\tNo event queued."); 02172 } else { 02173 aLog.append("\n\tEvents"); 02174 for (String key : _events.keySet()) { 02175 aLog.append("\n\t\tEvent: "); 02176 aLog.append(key); 02177 aLog.append(": "); 02178 aLog.append(_events.get(key)); 02179 } 02180 } 02181 return aLog.toString(); 02182 } 02183 } 02184 02185 static String toUnqualifiedName(String type, String qualifiedName) { 02186 String loType = type.toLowerCase(); 02187 String loQualifiedName = qualifiedName.toLowerCase(); 02188 if (loQualifiedName.endsWith(loType) && !(loQualifiedName.equals(loType))) { 02189 return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1); 02190 } 02191 return qualifiedName; 02192 } 02193 02194 public Map<String, ServiceInfo> getServices() { 02195 return _services; 02196 } 02197 02198 public void setLastThrottleIncrement(long lastThrottleIncrement) { 02199 this._lastThrottleIncrement = lastThrottleIncrement; 02200 } 02201 02202 public long getLastThrottleIncrement() { 02203 return _lastThrottleIncrement; 02204 } 02205 02206 public void setThrottle(int throttle) { 02207 this._throttle = throttle; 02208 } 02209 02210 public int getThrottle() { 02211 return _throttle; 02212 } 02213 02214 public static Random getRandom() { 02215 return _random; 02216 } 02217 02218 public void ioLock() { 02219 _ioLock.lock(); 02220 } 02221 02222 public void ioUnlock() { 02223 _ioLock.unlock(); 02224 } 02225 02226 public void setPlannedAnswer(DNSIncoming plannedAnswer) { 02227 this._plannedAnswer = plannedAnswer; 02228 } 02229 02230 public DNSIncoming getPlannedAnswer() { 02231 return _plannedAnswer; 02232 } 02233 02234 void setLocalHost(HostInfo localHost) { 02235 this._localHost = localHost; 02236 } 02237 02238 public Map<String, ServiceTypeEntry> getServiceTypes() { 02239 return _serviceTypes; 02240 } 02241 02242 public MulticastSocket getSocket() { 02243 return _socket; 02244 } 02245 02246 public InetAddress getGroup() { 02247 return _group; 02248 } 02249 02250 @Override 02251 public Delegate getDelegate() { 02252 return this._delegate; 02253 } 02254 02255 @Override 02256 public Delegate setDelegate(Delegate delegate) { 02257 Delegate previous = this._delegate; 02258 this._delegate = delegate; 02259 return previous; 02260 } 02261 02262 }