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