JmDNSImpl.java
Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends


zeroconf_jmdns
Author(s): Daniel Stonier
autogenerated on Tue Nov 6 2012 14:18:57