00001
00002
00003
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
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
00183
00184
00185 @Override
00186 public SubTypeEntry clone() {
00187
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
00218
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
00264
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
00277
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
00322
00323
00324
00329
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
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
00412
00413
00414
00415
00416
00417
00418
00419
00420
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
00469
00470 if (logger.isLoggable(Level.FINER)) {
00471 logger.finer("closeMulticastSocket()");
00472 }
00473 if (_socket != null) {
00474
00475 try {
00476 try {
00477 _socket.leaveGroup(_group);
00478 } catch (SocketException exception) {
00479
00480 }
00481 _socket.close();
00482
00483
00484
00485
00486
00487
00488 while (_incomingListener != null && _incomingListener.isAlive()) {
00489 synchronized (this) {
00490 try {
00491 if (_incomingListener != null && _incomingListener.isAlive()) {
00492
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
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
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
00750 final ServiceInfoImpl info = this.getServiceInfoFromCache(type, name, subtype, persistent);
00751
00752 this.startServiceInfoResolver(info);
00753
00754 return info;
00755 }
00756
00757 ServiceInfoImpl getServiceInfoFromCache(String type, String name, String subtype, boolean persistent) {
00758
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
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
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
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
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
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
00951
00952 serviceEvents.add(new ServiceEventImpl(this, record.getType(), toUnqualifiedName(record.getType(), record.getName()), record.getServiceInfo()));
00953 }
00954 }
00955 }
00956
00957 for (ServiceEvent serviceEvent : serviceEvents) {
00958 status.serviceAdded(serviceEvent);
00959 }
00960
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
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
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
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
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
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
01225 _listeners.add(listener);
01226
01227
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
01258 this.startServiceResolver(info.getType());
01259 }
01260 }
01261
01262
01273 public void updateRecord(long now, DNSRecord rec, Operation operation) {
01274
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
01286 {
01287 ServiceEvent event = rec.getServiceEvent(this);
01288 if ((event.getInfo() == null) || !event.getInfo().hasData()) {
01289
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
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
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
01376 if (newRecord.getTTL() == 0) {
01377 cacheOperation = Operation.Noop;
01378 cachedRecord.setWillExpireSoon(now);
01379
01380 } else {
01381 cacheOperation = Operation.Remove;
01382 this.getCache().removeDNSEntry(cachedRecord);
01383 }
01384 } else {
01385
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
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
01409 if (newRecord.getRecordType() == DNSRecordType.TYPE_PTR) {
01410
01411 boolean typeAdded = false;
01412 if (newRecord.isServicesDiscoveryMetaQuery()) {
01413
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
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
01446
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
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
01577
01578
01579 @Override
01580 public void purgeTimer() {
01581 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeTimer();
01582 }
01583
01584
01585
01586
01587
01588 @Override
01589 public void purgeStateTimer() {
01590 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).purgeStateTimer();
01591 }
01592
01593
01594
01595
01596
01597 @Override
01598 public void cancelTimer() {
01599 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelTimer();
01600 }
01601
01602
01603
01604
01605
01606 @Override
01607 public void cancelStateTimer() {
01608 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).cancelStateTimer();
01609 }
01610
01611
01612
01613
01614
01615 @Override
01616 public void startProber() {
01617 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startProber();
01618 }
01619
01620
01621
01622
01623
01624 @Override
01625 public void startAnnouncer() {
01626 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startAnnouncer();
01627 }
01628
01629
01630
01631
01632
01633 @Override
01634 public void startRenewer() {
01635 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startRenewer();
01636 }
01637
01638
01639
01640
01641
01642 @Override
01643 public void startCanceler() {
01644 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startCanceler();
01645 }
01646
01647
01648
01649
01650
01651 @Override
01652 public void startReaper() {
01653 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startReaper();
01654 }
01655
01656
01657
01658
01659
01660 @Override
01661 public void startServiceInfoResolver(ServiceInfoImpl info) {
01662 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceInfoResolver(info);
01663 }
01664
01665
01666
01667
01668
01669 @Override
01670 public void startTypeResolver() {
01671 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startTypeResolver();
01672 }
01673
01674
01675
01676
01677
01678 @Override
01679 public void startServiceResolver(String type) {
01680 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startServiceResolver(type);
01681 }
01682
01683
01684
01685
01686
01687 @Override
01688 public void startResponder(DNSIncoming in, int port) {
01689 DNSTaskStarter.Factory.getInstance().getStarter(this.getDns()).startResponder(in, port);
01690 }
01691
01692
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
01717
01718 if (this.isClosing() || this.isClosed() || this.isCanceling() || this.isCanceled()) {
01719 return;
01720 }
01721
01722
01723
01724 synchronized (_recoverLock) {
01725
01726
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
01745
01746 if (logger.isLoggable(Level.FINER)) {
01747 logger.finer(this.getName() + "recover() Cleanning up");
01748 }
01749
01750 logger.warning("RECOVERING");
01751
01752 this.purgeTimer();
01753
01754
01755 final Collection<ServiceInfo> oldServiceInfos = new ArrayList<ServiceInfo>(getServices().values());
01756
01757
01758 this.unregisterAllServices();
01759 this.disposeServiceCollectors();
01760
01761 this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT);
01762
01763
01764 this.purgeStateTimer();
01765
01766
01767
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
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
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
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
01834
01835 if (this.closeState()) {
01836
01837
01838
01839 logger.finer("Canceling the timer");
01840 this.cancelTimer();
01841
01842
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
01852 logger.finer("Canceling the state timer");
01853 this.cancelStateTimer();
01854
01855
01856 _executor.shutdown();
01857
01858
01859 this.closeMulticastSocket();
01860
01861
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
01943
01944
01945
01946
01947
01948
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
01969
01970
01971
01972
01973
01974
01975
01976
01977
01978
01979
01980
01981
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
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
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 }