001 /**
002 * Copyright 2003-2005 Arthur van Hoff, Rick Blair
003 *
004 * Licensed to the Apache Software Foundation (ASF) under one or more
005 * contributor license agreements. See the NOTICE file distributed with
006 * this work for additional information regarding copyright ownership.
007 * The ASF licenses this file to You under the Apache License, Version 2.0
008 * (the "License"); you may not use this file except in compliance with
009 * the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019 package org.apache.activemq.jmdns;
020
021 import java.io.IOException;
022 import java.net.DatagramPacket;
023 import java.net.InetAddress;
024 import java.net.MulticastSocket;
025 import java.util.*;
026 import java.util.logging.Level;
027 import java.util.logging.Logger;
028
029 // REMIND: multiple IP addresses
030
031 /**
032 * mDNS implementation in Java.
033 *
034 * @version %I%, %G%
035 * @author Arthur van Hoff, Rick Blair, Jeff Sonstein,
036 * Werner Randelshofer, Pierre Frisch, Scott Lewis
037 */
038 public class JmDNS
039 {
040 private static Logger logger = Logger.getLogger(JmDNS.class.toString());
041 /**
042 * The version of JmDNS.
043 */
044 public static String VERSION = "2.0";
045
046 /**
047 * This is the multicast group, we are listening to for multicast DNS messages.
048 */
049 private InetAddress group;
050 /**
051 * This is our multicast socket.
052 */
053 private MulticastSocket socket;
054
055 /**
056 * Used to fix live lock problem on unregester.
057 */
058
059 protected boolean closed = false;
060
061 /**
062 * Holds instances of JmDNS.DNSListener.
063 * Must by a synchronized collection, because it is updated from
064 * concurrent threads.
065 */
066 private List listeners;
067 /**
068 * Holds instances of ServiceListener's.
069 * Keys are Strings holding a fully qualified service type.
070 * Values are LinkedList's of ServiceListener's.
071 */
072 private Map serviceListeners;
073 /**
074 * Holds instances of ServiceTypeListener's.
075 */
076 private List typeListeners;
077
078
079 /**
080 * Cache for DNSEntry's.
081 */
082 private DNSCache cache;
083
084 /**
085 * This hashtable holds the services that have been registered.
086 * Keys are instances of String which hold an all lower-case version of the
087 * fully qualified service name.
088 * Values are instances of ServiceInfo.
089 */
090 Map services;
091
092 /**
093 * This hashtable holds the service types that have been registered or
094 * that have been received in an incoming datagram.
095 * Keys are instances of String which hold an all lower-case version of the
096 * fully qualified service type.
097 * Values hold the fully qualified service type.
098 */
099 Map serviceTypes;
100 /**
101 * This is the shutdown hook, we registered with the java runtime.
102 */
103 private Thread shutdown;
104
105 /**
106 * Handle on the local host
107 */
108 HostInfo localHost;
109
110 private Thread incomingListener = null;
111
112 /**
113 * Throttle count.
114 * This is used to count the overall number of probes sent by JmDNS.
115 * When the last throttle increment happened .
116 */
117 private int throttle;
118 /**
119 * Last throttle increment.
120 */
121 private long lastThrottleIncrement;
122
123 /**
124 * The timer is used to dispatch all outgoing messages of JmDNS.
125 * It is also used to dispatch maintenance tasks for the DNS cache.
126 */
127 private Timer timer;
128
129 /**
130 * The source for random values.
131 * This is used to introduce random delays in responses. This reduces the
132 * potential for collisions on the network.
133 */
134 private final static Random random = new Random();
135
136 /**
137 * This lock is used to coordinate processing of incoming and outgoing
138 * messages. This is needed, because the Rendezvous Conformance Test
139 * does not forgive race conditions.
140 */
141 private Object ioLock = new Object();
142
143 /**
144 * If an incoming package which needs an answer is truncated, we store it
145 * here. We add more incoming DNSRecords to it, until the JmDNS.Responder
146 * timer picks it up.
147 * Remind: This does not work well with multiple planned answers for packages
148 * that came in from different clients.
149 */
150 private DNSIncoming plannedAnswer;
151
152 // State machine
153 /**
154 * The state of JmDNS.
155 * <p/>
156 * For proper handling of concurrency, this variable must be
157 * changed only using methods advanceState(), revertState() and cancel().
158 */
159 private DNSState state = DNSState.PROBING_1;
160
161 /**
162 * Timer task associated to the host name.
163 * This is used to prevent from having multiple tasks associated to the host
164 * name at the same time.
165 */
166 TimerTask task;
167
168 /**
169 * This hashtable is used to maintain a list of service types being collected
170 * by this JmDNS instance.
171 * The key of the hashtable is a service type name, the value is an instance
172 * of JmDNS.ServiceCollector.
173 *
174 * @see #list
175 */
176 private HashMap serviceCollectors = new HashMap();
177
178 /**
179 * Create an instance of JmDNS.
180 */
181 public JmDNS() throws IOException
182 {
183 logger.finer("JmDNS instance created");
184 try
185 {
186 InetAddress addr = InetAddress.getLocalHost();
187 init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); // [PJYF Oct 14 2004] Why do we disallow the loopback address?
188 }
189 catch (IOException e)
190 {
191 init(null, "computer");
192 }
193 }
194
195 /**
196 * Create an instance of JmDNS and bind it to a
197 * specific network interface given its IP-address.
198 */
199 public JmDNS(InetAddress addr) throws IOException
200 {
201 try
202 {
203 init(addr, addr.getHostName());
204 }
205 catch (IOException e)
206 {
207 init(null, "computer");
208 }
209 }
210
211 /**
212 * Initialize everything.
213 *
214 * @param address The interface to which JmDNS binds to.
215 * @param name The host name of the interface.
216 */
217 private void init(InetAddress address, String name) throws IOException
218 {
219 // A host name with "." is illegal. so strip off everything and append .local.
220 int idx = name.indexOf(".");
221 if (idx > 0)
222 {
223 name = name.substring(0, idx);
224 }
225 name += ".local.";
226 // localHost to IP address binding
227 localHost = new HostInfo(address, name);
228
229 cache = new DNSCache(100);
230
231 listeners = Collections.synchronizedList(new ArrayList());
232 serviceListeners = new HashMap();
233 typeListeners = new ArrayList();
234
235 services = new Hashtable(20);
236 serviceTypes = new Hashtable(20);
237
238 timer = new Timer("JmDNS.Timer");
239 new RecordReaper().start();
240 shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
241 Runtime.getRuntime().addShutdownHook(shutdown);
242
243 incomingListener = new Thread(new SocketListener(), "JmDNS.SocketListener");
244
245 // Bind to multicast socket
246 openMulticastSocket(localHost);
247 start(services.values());
248 }
249
250 private void start(Collection serviceInfos)
251 {
252 state = DNSState.PROBING_1;
253 incomingListener.start();
254 new Prober().start();
255 for (Iterator iterator = serviceInfos.iterator(); iterator.hasNext();)
256 {
257 try
258 {
259 registerService(new ServiceInfo((ServiceInfo) iterator.next()));
260 }
261 catch (Exception exception)
262 {
263 logger.log(Level.WARNING, "start() Registration exception ", exception);
264 }
265 }
266 }
267
268 private void openMulticastSocket(HostInfo hostInfo) throws IOException
269 {
270 if (group == null)
271 {
272 group = InetAddress.getByName(DNSConstants.MDNS_GROUP);
273 }
274 if (socket != null)
275 {
276 this.closeMulticastSocket();
277 }
278 socket = new MulticastSocket(DNSConstants.MDNS_PORT);
279 if ((hostInfo != null) && (localHost.getInterface() != null))
280 {
281 socket.setNetworkInterface(hostInfo.getInterface());
282 }
283 socket.setTimeToLive(255);
284 socket.joinGroup(group);
285 }
286
287 private void closeMulticastSocket()
288 {
289 logger.finer("closeMulticastSocket()");
290 if (socket != null)
291 {
292 // close socket
293 try
294 {
295 socket.leaveGroup(group);
296 socket.close();
297 if (incomingListener != null)
298 {
299 incomingListener.join();
300 }
301 }
302 catch (Exception exception)
303 {
304 logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception);
305 }
306 socket = null;
307 }
308 }
309
310 // State machine
311 /**
312 * Sets the state and notifies all objects that wait on JmDNS.
313 */
314 synchronized void advanceState()
315 {
316 state = state.advance();
317 notifyAll();
318 }
319
320 /**
321 * Sets the state and notifies all objects that wait on JmDNS.
322 */
323 synchronized void revertState()
324 {
325 state = state.revert();
326 notifyAll();
327 }
328
329 /**
330 * Sets the state and notifies all objects that wait on JmDNS.
331 */
332 synchronized void cancel()
333 {
334 state = DNSState.CANCELED;
335 notifyAll();
336 }
337
338 /**
339 * Returns the current state of this info.
340 */
341 DNSState getState()
342 {
343 return state;
344 }
345
346
347 /**
348 * Return the DNSCache associated with the cache variable
349 */
350 DNSCache getCache()
351 {
352 return cache;
353 }
354
355 /**
356 * Return the HostName associated with this JmDNS instance.
357 * Note: May not be the same as what started. The host name is subject to
358 * negotiation.
359 */
360 public String getHostName()
361 {
362 return localHost.getName();
363 }
364
365 public HostInfo getLocalHost()
366 {
367 return localHost;
368 }
369
370 /**
371 * Return the address of the interface to which this instance of JmDNS is
372 * bound.
373 */
374 public InetAddress getInterface() throws IOException
375 {
376 return socket.getInterface();
377 }
378
379 /**
380 * Get service information. If the information is not cached, the method
381 * will block until updated information is received.
382 * <p/>
383 * Usage note: Do not call this method from the AWT event dispatcher thread.
384 * You will make the user interface unresponsive.
385 *
386 * @param type fully qualified service type, such as <code>_http._tcp.local.</code> .
387 * @param name unqualified service name, such as <code>foobar</code> .
388 * @return null if the service information cannot be obtained
389 */
390 public ServiceInfo getServiceInfo(String type, String name)
391 {
392 return getServiceInfo(type, name, 3 * 1000);
393 }
394
395 /**
396 * Get service information. If the information is not cached, the method
397 * will block for the given timeout until updated information is received.
398 * <p/>
399 * Usage note: If you call this method from the AWT event dispatcher thread,
400 * use a small timeout, or you will make the user interface unresponsive.
401 *
402 * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
403 * @param name unqualified service name, such as <code>foobar</code> .
404 * @param timeout timeout in milliseconds
405 * @return null if the service information cannot be obtained
406 */
407 public ServiceInfo getServiceInfo(String type, String name, int timeout)
408 {
409 ServiceInfo info = new ServiceInfo(type, name);
410 new ServiceInfoResolver(info).start();
411
412 try
413 {
414 long end = System.currentTimeMillis() + timeout;
415 long delay;
416 synchronized (info)
417 {
418 while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0)
419 {
420 info.wait(delay);
421 }
422 }
423 }
424 catch (InterruptedException e)
425 {
426 // empty
427 }
428
429 return (info.hasData()) ? info : null;
430 }
431
432 /**
433 * Request service information. The information about the service is
434 * requested and the ServiceListener.resolveService method is called as soon
435 * as it is available.
436 * <p/>
437 * Usage note: Do not call this method from the AWT event dispatcher thread.
438 * You will make the user interface unresponsive.
439 *
440 * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
441 * @param name unqualified service name, such as <code>foobar</code> .
442 */
443 public void requestServiceInfo(String type, String name)
444 {
445 requestServiceInfo(type, name, 3 * 1000);
446 }
447
448 /**
449 * Request service information. The information about the service is requested
450 * and the ServiceListener.resolveService method is called as soon as it is available.
451 *
452 * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
453 * @param name unqualified service name, such as <code>foobar</code> .
454 * @param timeout timeout in milliseconds
455 */
456 public void requestServiceInfo(String type, String name, int timeout)
457 {
458 registerServiceType(type);
459 ServiceInfo info = new ServiceInfo(type, name);
460 new ServiceInfoResolver(info).start();
461
462 try
463 {
464 long end = System.currentTimeMillis() + timeout;
465 long delay;
466 synchronized (info)
467 {
468 while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0)
469 {
470 info.wait(delay);
471 }
472 }
473 }
474 catch (InterruptedException e)
475 {
476 // empty
477 }
478 }
479
480 void handleServiceResolved(ServiceInfo info)
481 {
482 List list = (List) serviceListeners.get(info.type.toLowerCase());
483 if (list != null)
484 {
485 ServiceEvent event = new ServiceEvent(this, info.type, info.getName(), info);
486 // Iterate on a copy in case listeners will modify it
487 final ArrayList listCopy = new ArrayList(list);
488 for (Iterator iterator = listCopy.iterator(); iterator.hasNext();)
489 {
490 ((ServiceListener) iterator.next()).serviceResolved(event);
491 }
492 }
493 }
494
495 /**
496 * Listen for service types.
497 *
498 * @param listener listener for service types
499 */
500 public void addServiceTypeListener(ServiceTypeListener listener) throws IOException
501 {
502 synchronized (this)
503 {
504 typeListeners.remove(listener);
505 typeListeners.add(listener);
506 }
507
508 // report cached service types
509 for (Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();)
510 {
511 listener.serviceTypeAdded(new ServiceEvent(this, (String) iterator.next(), null, null));
512 }
513
514 new TypeResolver().start();
515 }
516
517 /**
518 * Remove listener for service types.
519 *
520 * @param listener listener for service types
521 */
522 public void removeServiceTypeListener(ServiceTypeListener listener)
523 {
524 synchronized (this)
525 {
526 typeListeners.remove(listener);
527 }
528 }
529
530 /**
531 * Listen for services of a given type. The type has to be a fully qualified
532 * type name such as <code>_http._tcp.local.</code>.
533 *
534 * @param type full qualified service type, such as <code>_http._tcp.local.</code>.
535 * @param listener listener for service updates
536 */
537 public void addServiceListener(String type, ServiceListener listener)
538 {
539 String lotype = type.toLowerCase();
540 removeServiceListener(lotype, listener);
541 List list = null;
542 synchronized (this)
543 {
544 list = (List) serviceListeners.get(lotype);
545 if (list == null)
546 {
547 list = Collections.synchronizedList(new LinkedList());
548 serviceListeners.put(lotype, list);
549 }
550 list.add(listener);
551 }
552
553 // report cached service types
554 for (Iterator i = cache.iterator(); i.hasNext();)
555 {
556 for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next())
557 {
558 DNSRecord rec = (DNSRecord) n.getValue();
559 if (rec.type == DNSConstants.TYPE_SRV)
560 {
561 if (rec.name.endsWith(type))
562 {
563 listener.serviceAdded(new ServiceEvent(this, type, toUnqualifiedName(type, rec.name), null));
564 }
565 }
566 }
567 }
568 new ServiceResolver(type).start();
569 }
570
571 /**
572 * Remove listener for services of a given type.
573 *
574 * @param listener listener for service updates
575 */
576 public void removeServiceListener(String type, ServiceListener listener)
577 {
578 type = type.toLowerCase();
579 List list = (List) serviceListeners.get(type);
580 if (list != null)
581 {
582 synchronized (this)
583 {
584 list.remove(listener);
585 if (list.size() == 0)
586 {
587 serviceListeners.remove(type);
588 }
589 }
590 }
591 }
592
593 /**
594 * Register a service. The service is registered for access by other jmdns clients.
595 * The name of the service may be changed to make it unique.
596 */
597 public void registerService(ServiceInfo info) throws IOException
598 {
599 registerServiceType(info.type);
600
601 // bind the service to this address
602 info.server = localHost.getName();
603 info.addr = localHost.getAddress();
604
605 synchronized (this)
606 {
607 makeServiceNameUnique(info);
608 services.put(info.getQualifiedName().toLowerCase(), info);
609 }
610
611 new /*Service*/Prober().start();
612 try
613 {
614 synchronized (info)
615 {
616 while (info.getState().compareTo(DNSState.ANNOUNCED) < 0)
617 {
618 info.wait();
619 }
620 }
621 }
622 catch (InterruptedException e)
623 {
624 //empty
625 }
626 logger.fine("registerService() JmDNS registered service as " + info);
627 }
628
629 /**
630 * Unregister a service. The service should have been registered.
631 */
632 public void unregisterService(ServiceInfo info)
633 {
634 synchronized (this)
635 {
636 services.remove(info.getQualifiedName().toLowerCase());
637 }
638 info.cancel();
639
640 // Note: We use this lock object to synchronize on it.
641 // Synchronizing on another object (e.g. the ServiceInfo) does
642 // not make sense, because the sole purpose of the lock is to
643 // wait until the canceler has finished. If we synchronized on
644 // the ServiceInfo or on the Canceler, we would block all
645 // accesses to synchronized methods on that object. This is not
646 // what we want!
647 Object lock = new Object();
648 new Canceler(info, lock).start();
649
650 // Remind: We get a deadlock here, if the Canceler does not run!
651 try
652 {
653 synchronized (lock)
654 {
655 lock.wait();
656 }
657 }
658 catch (InterruptedException e)
659 {
660 // empty
661 }
662 }
663
664 /**
665 * Unregister all services.
666 */
667 public void unregisterAllServices()
668 {
669 logger.finer("unregisterAllServices()");
670 if (services.size() == 0)
671 {
672 return;
673 }
674
675 Collection list;
676 synchronized (this)
677 {
678 list = new LinkedList(services.values());
679 services.clear();
680 }
681 for (Iterator iterator = list.iterator(); iterator.hasNext();)
682 {
683 ((ServiceInfo) iterator.next()).cancel();
684 }
685
686
687 Object lock = new Object();
688 new Canceler(list, lock).start();
689 // Remind: We get a livelock here, if the Canceler does not run!
690 try {
691 synchronized (lock) {
692 if (!closed) {
693 lock.wait();
694 }
695 }
696 } catch (InterruptedException e) {
697 // empty
698 }
699
700
701 }
702
703 /**
704 * Register a service type. If this service type was not already known,
705 * all service listeners will be notified of the new service type. Service types
706 * are automatically registered as they are discovered.
707 */
708 public void registerServiceType(String type)
709 {
710 String name = type.toLowerCase();
711 if (serviceTypes.get(name) == null)
712 {
713 if ((type.indexOf("._mdns._udp.") < 0) && !type.endsWith(".in-addr.arpa."))
714 {
715 Collection list;
716 synchronized (this)
717 {
718 serviceTypes.put(name, type);
719 list = new LinkedList(typeListeners);
720 }
721 for (Iterator iterator = list.iterator(); iterator.hasNext();)
722 {
723 ((ServiceTypeListener) iterator.next()).serviceTypeAdded(new ServiceEvent(this, type, null, null));
724 }
725 }
726 }
727 }
728
729 /**
730 * Generate a possibly unique name for a host using the information we
731 * have in the cache.
732 *
733 * @return returns true, if the name of the host had to be changed.
734 */
735 private boolean makeHostNameUnique(DNSRecord.Address host)
736 {
737 String originalName = host.getName();
738 long now = System.currentTimeMillis();
739
740 boolean collision;
741 do
742 {
743 collision = false;
744
745 // Check for collision in cache
746 for (DNSCache.CacheNode j = cache.find(host.getName().toLowerCase()); j != null; j = j.next())
747 {
748 DNSRecord a = (DNSRecord) j.getValue();
749 if (false)
750 {
751 host.name = incrementName(host.getName());
752 collision = true;
753 break;
754 }
755 }
756 }
757 while (collision);
758
759 if (originalName.equals(host.getName()))
760 {
761 return false;
762 }
763 else
764 {
765 return true;
766 }
767 }
768
769 /**
770 * Generate a possibly unique name for a service using the information we
771 * have in the cache.
772 *
773 * @return returns true, if the name of the service info had to be changed.
774 */
775 private boolean makeServiceNameUnique(ServiceInfo info)
776 {
777 String originalQualifiedName = info.getQualifiedName();
778 long now = System.currentTimeMillis();
779
780 boolean collision;
781 do
782 {
783 collision = false;
784
785 // Check for collision in cache
786 for (DNSCache.CacheNode j = cache.find(info.getQualifiedName().toLowerCase()); j != null; j = j.next())
787 {
788 DNSRecord a = (DNSRecord) j.getValue();
789 if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now))
790 {
791 DNSRecord.Service s = (DNSRecord.Service) a;
792 if (s.port != info.port || !s.server.equals(localHost.getName()))
793 {
794 logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + a + " s.server=" + s.server + " " + localHost.getName() + " equals:" + (s.server.equals(localHost.getName())));
795 info.setName(incrementName(info.getName()));
796 collision = true;
797 break;
798 }
799 }
800 }
801
802 // Check for collision with other service infos published by JmDNS
803 Object selfService = services.get(info.getQualifiedName().toLowerCase());
804 if (selfService != null && selfService != info)
805 {
806 info.setName(incrementName(info.getName()));
807 collision = true;
808 }
809 }
810 while (collision);
811
812 return !(originalQualifiedName.equals(info.getQualifiedName()));
813 }
814
815 String incrementName(String name)
816 {
817 try
818 {
819 int l = name.lastIndexOf('(');
820 int r = name.lastIndexOf(')');
821 if ((l >= 0) && (l < r))
822 {
823 name = name.substring(0, l) + "(" + (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")";
824 }
825 else
826 {
827 name += " (2)";
828 }
829 }
830 catch (NumberFormatException e)
831 {
832 name += " (2)";
833 }
834 return name;
835 }
836
837 /**
838 * Add a listener for a question. The listener will receive updates
839 * of answers to the question as they arrive, or from the cache if they
840 * are already available.
841 */
842 void addListener(DNSListener listener, DNSQuestion question)
843 {
844 long now = System.currentTimeMillis();
845
846 // add the new listener
847 synchronized (this)
848 {
849 listeners.add(listener);
850 }
851
852 // report existing matched records
853 if (question != null)
854 {
855 for (DNSCache.CacheNode i = cache.find(question.name); i != null; i = i.next())
856 {
857 DNSRecord c = (DNSRecord) i.getValue();
858 if (question.answeredBy(c) && !c.isExpired(now))
859 {
860 listener.updateRecord(this, now, c);
861 }
862 }
863 }
864 }
865
866 /**
867 * Remove a listener from all outstanding questions. The listener will no longer
868 * receive any updates.
869 */
870 void removeListener(DNSListener listener)
871 {
872 synchronized (this)
873 {
874 listeners.remove(listener);
875 }
876 }
877
878
879 // Remind: Method updateRecord should receive a better name.
880 /**
881 * Notify all listeners that a record was updated.
882 */
883 void updateRecord(long now, DNSRecord rec)
884 {
885 // We do not want to block the entire DNS while we are updating the record for each listener (service info)
886 List listenerList = null;
887 synchronized (this)
888 {
889 listenerList = new ArrayList(listeners);
890 }
891 for (Iterator iterator = listenerList.iterator(); iterator.hasNext();)
892 {
893 DNSListener listener = (DNSListener) iterator.next();
894 listener.updateRecord(this, now, rec);
895 }
896 if (rec.type == DNSConstants.TYPE_PTR || rec.type == DNSConstants.TYPE_SRV)
897 {
898 List serviceListenerList = null;
899 synchronized (this)
900 {
901 serviceListenerList = (List) serviceListeners.get(rec.name.toLowerCase());
902 // Iterate on a copy in case listeners will modify it
903 if (serviceListenerList != null)
904 {
905 serviceListenerList = new ArrayList(serviceListenerList);
906 }
907 }
908 if (serviceListenerList != null)
909 {
910 boolean expired = rec.isExpired(now);
911 String type = rec.getName();
912 String name = ((DNSRecord.Pointer) rec).getAlias();
913 // DNSRecord old = (DNSRecord)services.get(name.toLowerCase());
914 if (!expired)
915 {
916 // new record
917 ServiceEvent event = new ServiceEvent(this, type, toUnqualifiedName(type, name), null);
918 for (Iterator iterator = serviceListenerList.iterator(); iterator.hasNext();)
919 {
920 ((ServiceListener) iterator.next()).serviceAdded(event);
921 }
922 }
923 else
924 {
925 // expire record
926 ServiceEvent event = new ServiceEvent(this, type, toUnqualifiedName(type, name), null);
927 for (Iterator iterator = serviceListenerList.iterator(); iterator.hasNext();)
928 {
929 ((ServiceListener) iterator.next()).serviceRemoved(event);
930 }
931 }
932 }
933 }
934 }
935
936 /**
937 * Handle an incoming response. Cache answers, and pass them on to
938 * the appropriate questions.
939 */
940 private void handleResponse(DNSIncoming msg) throws IOException
941 {
942 long now = System.currentTimeMillis();
943
944 boolean hostConflictDetected = false;
945 boolean serviceConflictDetected = false;
946
947 for (Iterator i = msg.answers.iterator(); i.hasNext();)
948 {
949 boolean isInformative = false;
950 DNSRecord rec = (DNSRecord) i.next();
951 boolean expired = rec.isExpired(now);
952
953 // update the cache
954 DNSRecord c = (DNSRecord) cache.get(rec);
955 if (c != null)
956 {
957 if (expired)
958 {
959 isInformative = true;
960 cache.remove(c);
961 }
962 else
963 {
964 c.resetTTL(rec);
965 rec = c;
966 }
967 }
968 else
969 {
970 if (!expired)
971 {
972 isInformative = true;
973 cache.add(rec);
974 }
975 }
976 switch (rec.type)
977 {
978 case DNSConstants.TYPE_PTR:
979 // handle _mdns._udp records
980 if (rec.getName().indexOf("._mdns._udp.") >= 0)
981 {
982 if (!expired && rec.name.startsWith("_services._mdns._udp."))
983 {
984 isInformative = true;
985 registerServiceType(((DNSRecord.Pointer) rec).alias);
986 }
987 continue;
988 }
989 registerServiceType(rec.name);
990 break;
991 }
992
993 if ((rec.getType() == DNSConstants.TYPE_A) || (rec.getType() == DNSConstants.TYPE_AAAA))
994 {
995 hostConflictDetected |= rec.handleResponse(this);
996 }
997 else
998 {
999 serviceConflictDetected |= rec.handleResponse(this);
1000 }
1001
1002 // notify the listeners
1003 if (isInformative)
1004 {
1005 updateRecord(now, rec);
1006 }
1007 }
1008
1009 if (hostConflictDetected || serviceConflictDetected)
1010 {
1011 new Prober().start();
1012 }
1013 }
1014
1015 /**
1016 * Handle an incoming query. See if we can answer any part of it
1017 * given our service infos.
1018 */
1019 private void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException
1020 {
1021 // Track known answers
1022 boolean hostConflictDetected = false;
1023 boolean serviceConflictDetected = false;
1024 long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL;
1025 for (Iterator i = in.answers.iterator(); i.hasNext();)
1026 {
1027 DNSRecord answer = (DNSRecord) i.next();
1028 if ((answer.getType() == DNSConstants.TYPE_A) || (answer.getType() == DNSConstants.TYPE_AAAA))
1029 {
1030 hostConflictDetected |= answer.handleQuery(this, expirationTime);
1031 }
1032 else
1033 {
1034 serviceConflictDetected |= answer.handleQuery(this, expirationTime);
1035 }
1036 }
1037
1038 if (plannedAnswer != null)
1039 {
1040 plannedAnswer.append(in);
1041 }
1042 else
1043 {
1044 if (in.isTruncated())
1045 {
1046 plannedAnswer = in;
1047 }
1048
1049 new Responder(in, addr, port).start();
1050 }
1051
1052 if (hostConflictDetected || serviceConflictDetected)
1053 {
1054 new Prober().start();
1055 }
1056 }
1057
1058 /**
1059 * Add an answer to a question. Deal with the case when the
1060 * outgoing packet overflows
1061 */
1062 DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException
1063 {
1064 if (out == null)
1065 {
1066 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1067 }
1068 try
1069 {
1070 out.addAnswer(in, rec);
1071 }
1072 catch (IOException e)
1073 {
1074 out.flags |= DNSConstants.FLAGS_TC;
1075 out.id = in.id;
1076 out.finish();
1077 send(out);
1078
1079 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1080 out.addAnswer(in, rec);
1081 }
1082 return out;
1083 }
1084
1085
1086 /**
1087 * Send an outgoing multicast DNS message.
1088 */
1089 private void send(DNSOutgoing out) throws IOException
1090 {
1091 out.finish();
1092 if (!out.isEmpty())
1093 {
1094 DatagramPacket packet = new DatagramPacket(out.data, out.off, group, DNSConstants.MDNS_PORT);
1095
1096 try
1097 {
1098 DNSIncoming msg = new DNSIncoming(packet);
1099 logger.finest("send() JmDNS out:" + msg.print(true));
1100 }
1101 catch (IOException e)
1102 {
1103 logger.throwing(getClass().toString(), "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", e);
1104 }
1105 socket.send(packet);
1106 }
1107 }
1108
1109 /**
1110 * Listen for multicast packets.
1111 */
1112 class SocketListener implements Runnable
1113 {
1114 public void run()
1115 {
1116 try
1117 {
1118 byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE];
1119 DatagramPacket packet = new DatagramPacket(buf, buf.length);
1120 while (state != DNSState.CANCELED)
1121 {
1122 packet.setLength(buf.length);
1123 socket.receive(packet);
1124 if (state == DNSState.CANCELED)
1125 {
1126 break;
1127 }
1128 try
1129 {
1130 if (localHost.shouldIgnorePacket(packet))
1131 {
1132 continue;
1133 }
1134
1135 DNSIncoming msg = new DNSIncoming(packet);
1136 logger.finest("SocketListener.run() JmDNS in:" + msg.print(true));
1137
1138 synchronized (ioLock)
1139 {
1140 if (msg.isQuery())
1141 {
1142 if (packet.getPort() != DNSConstants.MDNS_PORT)
1143 {
1144 handleQuery(msg, packet.getAddress(), packet.getPort());
1145 }
1146 handleQuery(msg, group, DNSConstants.MDNS_PORT);
1147 }
1148 else
1149 {
1150 handleResponse(msg);
1151 }
1152 }
1153 }
1154 catch (IOException e)
1155 {
1156 logger.log(Level.WARNING, "run() exception ", e);
1157 }
1158 }
1159 }
1160 catch (IOException e)
1161 {
1162 if (state != DNSState.CANCELED)
1163 {
1164 logger.log(Level.WARNING, "run() exception ", e);
1165 recover();
1166 }
1167 }
1168 }
1169 }
1170
1171
1172 /**
1173 * Periodicaly removes expired entries from the cache.
1174 */
1175 private class RecordReaper extends TimerTask
1176 {
1177 public void start()
1178 {
1179 timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL);
1180 }
1181
1182 public void run()
1183 {
1184 synchronized (JmDNS.this)
1185 {
1186 if (state == DNSState.CANCELED)
1187 {
1188 return;
1189 }
1190 logger.finest("run() JmDNS reaping cache");
1191
1192 // Remove expired answers from the cache
1193 // -------------------------------------
1194 // To prevent race conditions, we defensively copy all cache
1195 // entries into a list.
1196 List list = new ArrayList();
1197 synchronized (cache)
1198 {
1199 for (Iterator i = cache.iterator(); i.hasNext();)
1200 {
1201 for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next())
1202 {
1203 list.add(n.getValue());
1204 }
1205 }
1206 }
1207 // Now, we remove them.
1208 long now = System.currentTimeMillis();
1209 for (Iterator i = list.iterator(); i.hasNext();)
1210 {
1211 DNSRecord c = (DNSRecord) i.next();
1212 if (c.isExpired(now))
1213 {
1214 updateRecord(now, c);
1215 cache.remove(c);
1216 }
1217 }
1218 }
1219 }
1220 }
1221
1222
1223 /**
1224 * The Prober sends three consecutive probes for all service infos
1225 * that needs probing as well as for the host name.
1226 * The state of each service info of the host name is advanced, when a probe has
1227 * been sent for it.
1228 * When the prober has run three times, it launches an Announcer.
1229 * <p/>
1230 * If a conflict during probes occurs, the affected service infos (and affected
1231 * host name) are taken away from the prober. This eventually causes the prober
1232 * tho cancel itself.
1233 */
1234 private class Prober extends TimerTask
1235 {
1236 /**
1237 * The state of the prober.
1238 */
1239 DNSState taskState = DNSState.PROBING_1;
1240
1241 public Prober()
1242 {
1243 // Associate the host name to this, if it needs probing
1244 if (state == DNSState.PROBING_1)
1245 {
1246 task = this;
1247 }
1248 // Associate services to this, if they need probing
1249 synchronized (JmDNS.this)
1250 {
1251 for (Iterator iterator = services.values().iterator(); iterator.hasNext();)
1252 {
1253 ServiceInfo info = (ServiceInfo) iterator.next();
1254 if (info.getState() == DNSState.PROBING_1)
1255 {
1256 info.task = this;
1257 }
1258 }
1259 }
1260 }
1261
1262
1263 public void start()
1264 {
1265 long now = System.currentTimeMillis();
1266 if (now - lastThrottleIncrement < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL)
1267 {
1268 throttle++;
1269 }
1270 else
1271 {
1272 throttle = 1;
1273 }
1274 lastThrottleIncrement = now;
1275
1276 if (state == DNSState.ANNOUNCED && throttle < DNSConstants.PROBE_THROTTLE_COUNT)
1277 {
1278 timer.schedule(this, random.nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL);
1279 }
1280 else
1281 {
1282 timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL);
1283 }
1284 }
1285
1286 public boolean cancel()
1287 {
1288 // Remove association from host name to this
1289 if (task == this)
1290 {
1291 task = null;
1292 }
1293
1294 // Remove associations from services to this
1295 synchronized (JmDNS.this)
1296 {
1297 for (Iterator i = services.values().iterator(); i.hasNext();)
1298 {
1299 ServiceInfo info = (ServiceInfo) i.next();
1300 if (info.task == this)
1301 {
1302 info.task = null;
1303 }
1304 }
1305 }
1306
1307 return super.cancel();
1308 }
1309
1310 public void run()
1311 {
1312 synchronized (ioLock)
1313 {
1314 DNSOutgoing out = null;
1315 try
1316 {
1317 // send probes for JmDNS itself
1318 if (state == taskState && task == this)
1319 {
1320 if (out == null)
1321 {
1322 out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1323 }
1324 out.addQuestion(new DNSQuestion(localHost.getName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
1325 DNSRecord answer = localHost.getDNS4AddressRecord();
1326 if (answer != null)
1327 {
1328 out.addAuthorativeAnswer(answer);
1329 }
1330 answer = localHost.getDNS6AddressRecord();
1331 if (answer != null)
1332 {
1333 out.addAuthorativeAnswer(answer);
1334 }
1335 advanceState();
1336 }
1337 // send probes for services
1338 // Defensively copy the services into a local list,
1339 // to prevent race conditions with methods registerService
1340 // and unregisterService.
1341 List list;
1342 synchronized (JmDNS.this)
1343 {
1344 list = new LinkedList(services.values());
1345 }
1346 for (Iterator i = list.iterator(); i.hasNext();)
1347 {
1348 ServiceInfo info = (ServiceInfo) i.next();
1349
1350 synchronized (info)
1351 {
1352 if (info.getState() == taskState && info.task == this)
1353 {
1354 info.advanceState();
1355 logger.fine("run() JmDNS probing " + info.getQualifiedName() + " state " + info.getState());
1356 if (out == null)
1357 {
1358 out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1359 out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
1360 }
1361 out.addAuthorativeAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
1362 }
1363 }
1364 }
1365 if (out != null)
1366 {
1367 logger.finer("run() JmDNS probing #" + taskState);
1368 send(out);
1369 }
1370 else
1371 {
1372 // If we have nothing to send, another timer taskState ahead
1373 // of us has done the job for us. We can cancel.
1374 cancel();
1375 return;
1376 }
1377 }
1378 catch (Throwable e)
1379 {
1380 logger.log(Level.WARNING, "run() exception ", e);
1381 recover();
1382 }
1383
1384 taskState = taskState.advance();
1385 if (!taskState.isProbing())
1386 {
1387 cancel();
1388
1389 new Announcer().start();
1390 }
1391 }
1392 }
1393
1394 }
1395
1396 /**
1397 * The Announcer sends an accumulated query of all announces, and advances
1398 * the state of all serviceInfos, for which it has sent an announce.
1399 * The Announcer also sends announcements and advances the state of JmDNS itself.
1400 * <p/>
1401 * When the announcer has run two times, it finishes.
1402 */
1403 private class Announcer extends TimerTask
1404 {
1405 /**
1406 * The state of the announcer.
1407 */
1408 DNSState taskState = DNSState.ANNOUNCING_1;
1409
1410 public Announcer()
1411 {
1412 // Associate host to this, if it needs announcing
1413 if (state == DNSState.ANNOUNCING_1)
1414 {
1415 task = this;
1416 }
1417 // Associate services to this, if they need announcing
1418 synchronized (JmDNS.this)
1419 {
1420 for (Iterator s = services.values().iterator(); s.hasNext();)
1421 {
1422 ServiceInfo info = (ServiceInfo) s.next();
1423 if (info.getState() == DNSState.ANNOUNCING_1)
1424 {
1425 info.task = this;
1426 }
1427 }
1428 }
1429 }
1430
1431 public void start()
1432 {
1433 timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
1434 }
1435
1436 public boolean cancel()
1437 {
1438 // Remove association from host to this
1439 if (task == this)
1440 {
1441 task = null;
1442 }
1443
1444 // Remove associations from services to this
1445 synchronized (JmDNS.this)
1446 {
1447 for (Iterator i = services.values().iterator(); i.hasNext();)
1448 {
1449 ServiceInfo info = (ServiceInfo) i.next();
1450 if (info.task == this)
1451 {
1452 info.task = null;
1453 }
1454 }
1455 }
1456
1457 return super.cancel();
1458 }
1459
1460 public void run()
1461 {
1462 DNSOutgoing out = null;
1463 try
1464 {
1465 // send probes for JmDNS itself
1466 if (state == taskState)
1467 {
1468 if (out == null)
1469 {
1470 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1471 }
1472 DNSRecord answer = localHost.getDNS4AddressRecord();
1473 if (answer != null)
1474 {
1475 out.addAnswer(answer, 0);
1476 }
1477 answer = localHost.getDNS6AddressRecord();
1478 if (answer != null)
1479 {
1480 out.addAnswer(answer, 0);
1481 }
1482 advanceState();
1483 }
1484 // send announces for services
1485 // Defensively copy the services into a local list,
1486 // to prevent race conditions with methods registerService
1487 // and unregisterService.
1488 List list;
1489 synchronized (JmDNS.this)
1490 {
1491 list = new ArrayList(services.values());
1492 }
1493 for (Iterator i = list.iterator(); i.hasNext();)
1494 {
1495 ServiceInfo info = (ServiceInfo) i.next();
1496 synchronized (info)
1497 {
1498 if (info.getState() == taskState && info.task == this)
1499 {
1500 info.advanceState();
1501 logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState());
1502 if (out == null)
1503 {
1504 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1505 }
1506 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0);
1507 out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0);
1508 out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.text), 0);
1509 }
1510 }
1511 }
1512 if (out != null)
1513 {
1514 logger.finer("run() JmDNS announcing #" + taskState);
1515 send(out);
1516 }
1517 else
1518 {
1519 // If we have nothing to send, another timer taskState ahead
1520 // of us has done the job for us. We can cancel.
1521 cancel();
1522 }
1523 }
1524 catch (Throwable e)
1525 {
1526 logger.log(Level.WARNING, "run() exception ", e);
1527 recover();
1528 }
1529
1530 taskState = taskState.advance();
1531 if (!taskState.isAnnouncing())
1532 {
1533 cancel();
1534
1535 new Renewer().start();
1536 }
1537 }
1538 }
1539
1540 /**
1541 * The Renewer is there to send renewal announcment when the record expire for ours infos.
1542 */
1543 private class Renewer extends TimerTask
1544 {
1545 /**
1546 * The state of the announcer.
1547 */
1548 DNSState taskState = DNSState.ANNOUNCED;
1549
1550 public Renewer()
1551 {
1552 // Associate host to this, if it needs renewal
1553 if (state == DNSState.ANNOUNCED)
1554 {
1555 task = this;
1556 }
1557 // Associate services to this, if they need renewal
1558 synchronized (JmDNS.this)
1559 {
1560 for (Iterator s = services.values().iterator(); s.hasNext();)
1561 {
1562 ServiceInfo info = (ServiceInfo) s.next();
1563 if (info.getState() == DNSState.ANNOUNCED)
1564 {
1565 info.task = this;
1566 }
1567 }
1568 }
1569 }
1570
1571 public void start()
1572 {
1573 timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL);
1574 }
1575
1576 public boolean cancel()
1577 {
1578 // Remove association from host to this
1579 if (task == this)
1580 {
1581 task = null;
1582 }
1583
1584 // Remove associations from services to this
1585 synchronized (JmDNS.this)
1586 {
1587 for (Iterator i = services.values().iterator(); i.hasNext();)
1588 {
1589 ServiceInfo info = (ServiceInfo) i.next();
1590 if (info.task == this)
1591 {
1592 info.task = null;
1593 }
1594 }
1595 }
1596
1597 return super.cancel();
1598 }
1599
1600 public void run()
1601 {
1602 DNSOutgoing out = null;
1603 try
1604 {
1605 // send probes for JmDNS itself
1606 if (state == taskState)
1607 {
1608 if (out == null)
1609 {
1610 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1611 }
1612 DNSRecord answer = localHost.getDNS4AddressRecord();
1613 if (answer != null)
1614 {
1615 out.addAnswer(answer, 0);
1616 }
1617 answer = localHost.getDNS6AddressRecord();
1618 if (answer != null)
1619 {
1620 out.addAnswer(answer, 0);
1621 }
1622 advanceState();
1623 }
1624 // send announces for services
1625 // Defensively copy the services into a local list,
1626 // to prevent race conditions with methods registerService
1627 // and unregisterService.
1628 List list;
1629 synchronized (JmDNS.this)
1630 {
1631 list = new ArrayList(services.values());
1632 }
1633 for (Iterator i = list.iterator(); i.hasNext();)
1634 {
1635 ServiceInfo info = (ServiceInfo) i.next();
1636 synchronized (info)
1637 {
1638 if (info.getState() == taskState && info.task == this)
1639 {
1640 info.advanceState();
1641 logger.finer("run() JmDNS announced " + info.getQualifiedName() + " state " + info.getState());
1642 if (out == null)
1643 {
1644 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1645 }
1646 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0);
1647 out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0);
1648 out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.text), 0);
1649 }
1650 }
1651 }
1652 if (out != null)
1653 {
1654 logger.finer("run() JmDNS announced");
1655 send(out);
1656 }
1657 else
1658 {
1659 // If we have nothing to send, another timer taskState ahead
1660 // of us has done the job for us. We can cancel.
1661 cancel();
1662 }
1663 }
1664 catch (Throwable e)
1665 {
1666 logger.log(Level.WARNING, "run() exception ", e);
1667 recover();
1668 }
1669
1670 taskState = taskState.advance();
1671 if (!taskState.isAnnounced())
1672 {
1673 cancel();
1674
1675 }
1676 }
1677 }
1678
1679 /**
1680 * The Responder sends a single answer for the specified service infos
1681 * and for the host name.
1682 */
1683 private class Responder extends TimerTask
1684 {
1685 private DNSIncoming in;
1686 private InetAddress addr;
1687 private int port;
1688
1689 public Responder(DNSIncoming in, InetAddress addr, int port)
1690 {
1691 this.in = in;
1692 this.addr = addr;
1693 this.port = port;
1694 }
1695
1696 public void start()
1697 {
1698 // According to draft-cheshire-dnsext-multicastdns.txt
1699 // chapter "8 Responding":
1700 // We respond immediately if we know for sure, that we are
1701 // the only one who can respond to the query.
1702 // In all other cases, we respond within 20-120 ms.
1703 //
1704 // According to draft-cheshire-dnsext-multicastdns.txt
1705 // chapter "7.2 Multi-Packet Known Answer Suppression":
1706 // We respond after 20-120 ms if the query is truncated.
1707
1708 boolean iAmTheOnlyOne = true;
1709 for (Iterator i = in.questions.iterator(); i.hasNext();)
1710 {
1711 DNSEntry entry = (DNSEntry) i.next();
1712 if (entry instanceof DNSQuestion)
1713 {
1714 DNSQuestion q = (DNSQuestion) entry;
1715 logger.finest("start() question=" + q);
1716 iAmTheOnlyOne &= (q.type == DNSConstants.TYPE_SRV
1717 || q.type == DNSConstants.TYPE_TXT
1718 || q.type == DNSConstants.TYPE_A
1719 || q.type == DNSConstants.TYPE_AAAA
1720 || localHost.getName().equalsIgnoreCase(q.name)
1721 || services.containsKey(q.name.toLowerCase()));
1722 if (!iAmTheOnlyOne)
1723 {
1724 break;
1725 }
1726 }
1727 }
1728 int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + random.nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival();
1729 if (delay < 0)
1730 {
1731 delay = 0;
1732 }
1733 logger.finest("start() Responder chosen delay=" + delay);
1734 timer.schedule(this, delay);
1735 }
1736
1737 public void run()
1738 {
1739 synchronized (ioLock)
1740 {
1741 if (plannedAnswer == in)
1742 {
1743 plannedAnswer = null;
1744 }
1745
1746 // We use these sets to prevent duplicate records
1747 // FIXME - This should be moved into DNSOutgoing
1748 HashSet questions = new HashSet();
1749 HashSet answers = new HashSet();
1750
1751
1752 if (state == DNSState.ANNOUNCED)
1753 {
1754 try
1755 {
1756 long now = System.currentTimeMillis();
1757 long expirationTime = now + 1; //=now+DNSConstants.KNOWN_ANSWER_TTL;
1758 boolean isUnicast = (port != DNSConstants.MDNS_PORT);
1759
1760
1761 // Answer questions
1762 for (Iterator iterator = in.questions.iterator(); iterator.hasNext();)
1763 {
1764 DNSEntry entry = (DNSEntry) iterator.next();
1765 if (entry instanceof DNSQuestion)
1766 {
1767 DNSQuestion q = (DNSQuestion) entry;
1768
1769 // for unicast responses the question must be included
1770 if (isUnicast)
1771 {
1772 //out.addQuestion(q);
1773 questions.add(q);
1774 }
1775
1776 int type = q.type;
1777 if (type == DNSConstants.TYPE_ANY || type == DNSConstants.TYPE_SRV)
1778 { // I ama not sure of why there is a special case here [PJYF Oct 15 2004]
1779 if (localHost.getName().equalsIgnoreCase(q.getName()))
1780 {
1781 // type = DNSConstants.TYPE_A;
1782 DNSRecord answer = localHost.getDNS4AddressRecord();
1783 if (answer != null)
1784 {
1785 answers.add(answer);
1786 }
1787 answer = localHost.getDNS6AddressRecord();
1788 if (answer != null)
1789 {
1790 answers.add(answer);
1791 }
1792 type = DNSConstants.TYPE_IGNORE;
1793 }
1794 else
1795 {
1796 if (serviceTypes.containsKey(q.getName().toLowerCase()))
1797 {
1798 type = DNSConstants.TYPE_PTR;
1799 }
1800 }
1801 }
1802
1803 switch (type)
1804 {
1805 case DNSConstants.TYPE_A:
1806 {
1807 // Answer a query for a domain name
1808 //out = addAnswer( in, addr, port, out, host );
1809 DNSRecord answer = localHost.getDNS4AddressRecord();
1810 if (answer != null)
1811 {
1812 answers.add(answer);
1813 }
1814 break;
1815 }
1816 case DNSConstants.TYPE_AAAA:
1817 {
1818 // Answer a query for a domain name
1819 DNSRecord answer = localHost.getDNS6AddressRecord();
1820 if (answer != null)
1821 {
1822 answers.add(answer);
1823 }
1824 break;
1825 }
1826 case DNSConstants.TYPE_PTR:
1827 {
1828 // Answer a query for services of a given type
1829
1830 // find matching services
1831 for (Iterator serviceIterator = services.values().iterator(); serviceIterator.hasNext();)
1832 {
1833 ServiceInfo info = (ServiceInfo) serviceIterator.next();
1834 if (info.getState() == DNSState.ANNOUNCED)
1835 {
1836 if (q.name.equalsIgnoreCase(info.type))
1837 {
1838 DNSRecord answer = localHost.getDNS4AddressRecord();
1839 if (answer != null)
1840 {
1841 answers.add(answer);
1842 }
1843 answer = localHost.getDNS6AddressRecord();
1844 if (answer != null)
1845 {
1846 answers.add(answer);
1847 }
1848 answers.add(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
1849 answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
1850 answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.text));
1851 }
1852 }
1853 }
1854 if (q.name.equalsIgnoreCase("_services._mdns._udp.local."))
1855 {
1856 for (Iterator serviceTypeIterator = serviceTypes.values().iterator(); serviceTypeIterator.hasNext();)
1857 {
1858 answers.add(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next()));
1859 }
1860 }
1861 break;
1862 }
1863 case DNSConstants.TYPE_SRV:
1864 case DNSConstants.TYPE_ANY:
1865 case DNSConstants.TYPE_TXT:
1866 {
1867 ServiceInfo info = (ServiceInfo) services.get(q.name.toLowerCase());
1868 if (info != null && info.getState() == DNSState.ANNOUNCED)
1869 {
1870 DNSRecord answer = localHost.getDNS4AddressRecord();
1871 if (answer != null)
1872 {
1873 answers.add(answer);
1874 }
1875 answer = localHost.getDNS6AddressRecord();
1876 if (answer != null)
1877 {
1878 answers.add(answer);
1879 }
1880 answers.add(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
1881 answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
1882 answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.text));
1883 }
1884 break;
1885 }
1886 default :
1887 {
1888 //System.out.println("JmDNSResponder.unhandled query:"+q);
1889 break;
1890 }
1891 }
1892 }
1893 }
1894
1895
1896 // remove known answers, if the ttl is at least half of
1897 // the correct value. (See Draft Cheshire chapter 7.1.).
1898 for (Iterator i = in.answers.iterator(); i.hasNext();)
1899 {
1900 DNSRecord knownAnswer = (DNSRecord) i.next();
1901 if (knownAnswer.ttl > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer))
1902 {
1903 logger.log(Level.FINER, "JmDNS Responder Known Answer Removed");
1904 }
1905 }
1906
1907
1908 // responde if we have answers
1909 if (answers.size() != 0)
1910 {
1911 logger.finer("run() JmDNS responding");
1912 DNSOutgoing out = null;
1913 if (isUnicast)
1914 {
1915 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false);
1916 }
1917
1918 for (Iterator i = questions.iterator(); i.hasNext();)
1919 {
1920 out.addQuestion((DNSQuestion) i.next());
1921 }
1922 for (Iterator i = answers.iterator(); i.hasNext();)
1923 {
1924 out = addAnswer(in, addr, port, out, (DNSRecord) i.next());
1925 }
1926 send(out);
1927 }
1928 cancel();
1929 }
1930 catch (Throwable e)
1931 {
1932 logger.log(Level.WARNING, "run() exception ", e);
1933 close();
1934 }
1935 }
1936 }
1937 }
1938 }
1939
1940 /**
1941 * Helper class to resolve service types.
1942 * <p/>
1943 * The TypeResolver queries three times consecutively for service types, and then
1944 * removes itself from the timer.
1945 * <p/>
1946 * The TypeResolver will run only if JmDNS is in state ANNOUNCED.
1947 */
1948 private class TypeResolver extends TimerTask
1949 {
1950 public void start()
1951 {
1952 timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
1953 }
1954
1955 /**
1956 * Counts the number of queries that were sent.
1957 */
1958 int count = 0;
1959
1960 public void run()
1961 {
1962 try
1963 {
1964 if (state == DNSState.ANNOUNCED)
1965 {
1966 if (++count < 3)
1967 {
1968 logger.finer("run() JmDNS querying type");
1969 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1970 out.addQuestion(new DNSQuestion("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
1971 for (Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();)
1972 {
1973 out.addAnswer(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) iterator.next()), 0);
1974 }
1975 send(out);
1976 }
1977 else
1978 {
1979 // After three queries, we can quit.
1980 cancel();
1981 }
1982 ;
1983 }
1984 else
1985 {
1986 if (state == DNSState.CANCELED)
1987 {
1988 cancel();
1989 }
1990 }
1991 }
1992 catch (Throwable e)
1993 {
1994 logger.log(Level.WARNING, "run() exception ", e);
1995 recover();
1996 }
1997 }
1998 }
1999
2000 /**
2001 * The ServiceResolver queries three times consecutively for services of
2002 * a given type, and then removes itself from the timer.
2003 * <p/>
2004 * The ServiceResolver will run only if JmDNS is in state ANNOUNCED.
2005 * REMIND: Prevent having multiple service resolvers for the same type in the
2006 * timer queue.
2007 */
2008 private class ServiceResolver extends TimerTask
2009 {
2010 /**
2011 * Counts the number of queries being sent.
2012 */
2013 int count = 0;
2014 private String type;
2015
2016 public ServiceResolver(String type)
2017 {
2018 this.type = type;
2019 }
2020
2021 public void start()
2022 {
2023 timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
2024 }
2025
2026 public void run()
2027 {
2028 try
2029 {
2030 if (state == DNSState.ANNOUNCED)
2031 {
2032 if (count++ < 3)
2033 {
2034 logger.finer("run() JmDNS querying service");
2035 long now = System.currentTimeMillis();
2036 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
2037 out.addQuestion(new DNSQuestion(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
2038 for (Iterator s = services.values().iterator(); s.hasNext();)
2039 {
2040 final ServiceInfo info = (ServiceInfo) s.next();
2041 try
2042 {
2043 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), now);
2044 }
2045 catch (IOException ee)
2046 {
2047 break;
2048 }
2049 }
2050 send(out);
2051 }
2052 else
2053 {
2054 // After three queries, we can quit.
2055 cancel();
2056 }
2057 ;
2058 }
2059 else
2060 {
2061 if (state == DNSState.CANCELED)
2062 {
2063 cancel();
2064 }
2065 }
2066 }
2067 catch (Throwable e)
2068 {
2069 logger.log(Level.WARNING, "run() exception ", e);
2070 recover();
2071 }
2072 }
2073 }
2074
2075 /**
2076 * The ServiceInfoResolver queries up to three times consecutively for
2077 * a service info, and then removes itself from the timer.
2078 * <p/>
2079 * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED.
2080 * REMIND: Prevent having multiple service resolvers for the same info in the
2081 * timer queue.
2082 */
2083 private class ServiceInfoResolver extends TimerTask
2084 {
2085 /**
2086 * Counts the number of queries being sent.
2087 */
2088 int count = 0;
2089 private ServiceInfo info;
2090
2091 public ServiceInfoResolver(ServiceInfo info)
2092 {
2093 this.info = info;
2094 info.dns = JmDNS.this;
2095 addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
2096 }
2097
2098 public void start()
2099 {
2100 timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
2101 }
2102
2103 public void run()
2104 {
2105 try
2106 {
2107 if (state == DNSState.ANNOUNCED)
2108 {
2109 if (count++ < 3 && !info.hasData())
2110 {
2111 long now = System.currentTimeMillis();
2112 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
2113 out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN));
2114 out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN));
2115 if (info.server != null)
2116 {
2117 out.addQuestion(new DNSQuestion(info.server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN));
2118 }
2119 out.addAnswer((DNSRecord) cache.get(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN), now);
2120 out.addAnswer((DNSRecord) cache.get(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN), now);
2121 if (info.server != null)
2122 {
2123 out.addAnswer((DNSRecord) cache.get(info.server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN), now);
2124 }
2125 send(out);
2126 }
2127 else
2128 {
2129 // After three queries, we can quit.
2130 cancel();
2131 removeListener(info);
2132 }
2133 ;
2134 }
2135 else
2136 {
2137 if (state == DNSState.CANCELED)
2138 {
2139 cancel();
2140 removeListener(info);
2141 }
2142 }
2143 }
2144 catch (Throwable e)
2145 {
2146 logger.log(Level.WARNING, "run() exception ", e);
2147 recover();
2148 }
2149 }
2150 }
2151
2152 /**
2153 * The Canceler sends two announces with TTL=0 for the specified services.
2154 */
2155 private class Canceler extends TimerTask
2156 {
2157 /**
2158 * Counts the number of announces being sent.
2159 */
2160 int count = 0;
2161 /**
2162 * The services that need cancelling.
2163 * Note: We have to use a local variable here, because the services
2164 * that are canceled, are removed immediately from variable JmDNS.services.
2165 */
2166 private ServiceInfo[] infos;
2167 /**
2168 * We call notifyAll() on the lock object, when we have canceled the
2169 * service infos.
2170 * This is used by method JmDNS.unregisterService() and
2171 * JmDNS.unregisterAllServices, to ensure that the JmDNS
2172 * socket stays open until the Canceler has canceled all services.
2173 * <p/>
2174 * Note: We need this lock, because ServiceInfos do the transition from
2175 * state ANNOUNCED to state CANCELED before we get here. We could get
2176 * rid of this lock, if we added a state named CANCELLING to DNSState.
2177 */
2178 private Object lock;
2179 int ttl = 0;
2180
2181 public Canceler(ServiceInfo info, Object lock)
2182 {
2183 this.infos = new ServiceInfo[]{info};
2184 this.lock = lock;
2185 addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
2186 }
2187
2188 public Canceler(ServiceInfo[] infos, Object lock)
2189 {
2190 this.infos = infos;
2191 this.lock = lock;
2192 }
2193
2194 public Canceler(Collection infos, Object lock)
2195 {
2196 this.infos = (ServiceInfo[]) infos.toArray(new ServiceInfo[infos.size()]);
2197 this.lock = lock;
2198 }
2199
2200 public void start()
2201 {
2202 timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
2203 }
2204
2205 public void run()
2206 {
2207 try
2208 {
2209 if (++count < 3)
2210 {
2211 logger.finer("run() JmDNS canceling service");
2212 // announce the service
2213 //long now = System.currentTimeMillis();
2214 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
2215 for (int i = 0; i < infos.length; i++)
2216 {
2217 ServiceInfo info = infos[i];
2218 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, ttl, info.getQualifiedName()), 0);
2219 out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, ttl, info.priority, info.weight, info.port, localHost.getName()), 0);
2220 out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, ttl, info.text), 0);
2221 DNSRecord answer = localHost.getDNS4AddressRecord();
2222 if (answer != null)
2223 {
2224 out.addAnswer(answer, 0);
2225 }
2226 answer = localHost.getDNS6AddressRecord();
2227 if (answer != null)
2228 {
2229 out.addAnswer(answer, 0);
2230 }
2231 }
2232 send(out);
2233 }
2234 else
2235 {
2236 // After three successful announcements, we are finished.
2237 synchronized (lock)
2238 {
2239 closed=true;
2240 lock.notifyAll();
2241 }
2242 cancel();
2243 }
2244 }
2245 catch (Throwable e)
2246 {
2247 logger.log(Level.WARNING, "run() exception ", e);
2248 recover();
2249 }
2250 }
2251 }
2252
2253 // REMIND: Why is this not an anonymous inner class?
2254 /**
2255 * Shutdown operations.
2256 */
2257 private class Shutdown implements Runnable
2258 {
2259 public void run()
2260 {
2261 shutdown = null;
2262 close();
2263 }
2264 }
2265
2266 /**
2267 * Recover jmdns when there is an error.
2268 */
2269 protected void recover()
2270 {
2271 logger.finer("recover()");
2272 // We have an IO error so lets try to recover if anything happens lets close it.
2273 // This should cover the case of the IP address changing under our feet
2274 if (DNSState.CANCELED != state)
2275 {
2276 synchronized (this)
2277 { // Synchronize only if we are not already in process to prevent dead locks
2278 //
2279 logger.finer("recover() Cleanning up");
2280 // Stop JmDNS
2281 state = DNSState.CANCELED; // This protects against recursive calls
2282
2283 // We need to keep a copy for reregistration
2284 Collection oldServiceInfos = new ArrayList(services.values());
2285
2286 // Cancel all services
2287 unregisterAllServices();
2288 disposeServiceCollectors();
2289 //
2290 // close multicast socket
2291 closeMulticastSocket();
2292 //
2293 cache.clear();
2294 logger.finer("recover() All is clean");
2295 //
2296 // All is clear now start the services
2297 //
2298 try
2299 {
2300 openMulticastSocket(localHost);
2301 start(oldServiceInfos);
2302 }
2303 catch (Exception exception)
2304 {
2305 logger.log(Level.WARNING, "recover() Start services exception ", exception);
2306 }
2307 logger.log(Level.WARNING, "recover() We are back!");
2308 }
2309 }
2310 }
2311
2312 /**
2313 * Close down jmdns. Release all resources and unregister all services.
2314 */
2315 public void close()
2316 {
2317 if (state != DNSState.CANCELED)
2318 {
2319 synchronized (this)
2320 { // Synchronize only if we are not already in process to prevent dead locks
2321 // Stop JmDNS
2322 state = DNSState.CANCELED; // This protects against recursive calls
2323
2324 unregisterAllServices();
2325 disposeServiceCollectors();
2326
2327 // close socket
2328 closeMulticastSocket();
2329
2330 // Stop the timer
2331 timer.cancel();
2332
2333 // remove the shutdown hook
2334 if (shutdown != null)
2335 {
2336 Runtime.getRuntime().removeShutdownHook(shutdown);
2337 }
2338
2339 }
2340 }
2341 }
2342
2343 /**
2344 * List cache entries, for debugging only.
2345 */
2346 void print()
2347 {
2348 System.out.println("---- cache ----");
2349 cache.print();
2350 System.out.println();
2351 }
2352
2353 /**
2354 * List Services and serviceTypes.
2355 * Debugging Only
2356 */
2357
2358 public void printServices()
2359 {
2360 System.err.println(toString());
2361 }
2362
2363 public String toString()
2364 {
2365 StringBuffer aLog = new StringBuffer();
2366 aLog.append("\t---- Services -----");
2367 if (services != null)
2368 {
2369 for (Iterator k = services.keySet().iterator(); k.hasNext();)
2370 {
2371 Object key = k.next();
2372 aLog.append("\n\t\tService: " + key + ": " + services.get(key));
2373 }
2374 }
2375 aLog.append("\n");
2376 aLog.append("\t---- Types ----");
2377 if (serviceTypes != null)
2378 {
2379 for (Iterator k = serviceTypes.keySet().iterator(); k.hasNext();)
2380 {
2381 Object key = k.next();
2382 aLog.append("\n\t\tType: " + key + ": " + serviceTypes.get(key));
2383 }
2384 }
2385 aLog.append("\n");
2386 aLog.append(cache.toString());
2387 aLog.append("\n");
2388 aLog.append("\t---- Service Collectors ----");
2389 if (serviceCollectors != null)
2390 {
2391 synchronized (serviceCollectors)
2392 {
2393 for (Iterator k = serviceCollectors.keySet().iterator(); k.hasNext();)
2394 {
2395 Object key = k.next();
2396 aLog.append("\n\t\tService Collector: " + key + ": " + serviceCollectors.get(key));
2397 }
2398 serviceCollectors.clear();
2399 }
2400 }
2401 return aLog.toString();
2402 }
2403
2404 /**
2405 * Returns a list of service infos of the specified type.
2406 *
2407 * @param type Service type name, such as <code>_http._tcp.local.</code>.
2408 * @return An array of service instance names.
2409 */
2410 public ServiceInfo[] list(String type)
2411 {
2412 // Implementation note: The first time a list for a given type is
2413 // requested, a ServiceCollector is created which collects service
2414 // infos. This greatly speeds up the performance of subsequent calls
2415 // to this method. The caveats are, that 1) the first call to this method
2416 // for a given type is slow, and 2) we spawn a ServiceCollector
2417 // instance for each service type which increases network traffic a
2418 // little.
2419
2420 ServiceCollector collector;
2421
2422 boolean newCollectorCreated;
2423 synchronized (serviceCollectors)
2424 {
2425 collector = (ServiceCollector) serviceCollectors.get(type);
2426 if (collector == null)
2427 {
2428 collector = new ServiceCollector(type);
2429 serviceCollectors.put(type, collector);
2430 addServiceListener(type, collector);
2431 newCollectorCreated = true;
2432 }
2433 else
2434 {
2435 newCollectorCreated = false;
2436 }
2437 }
2438
2439 // After creating a new ServiceCollector, we collect service infos for
2440 // 200 milliseconds. This should be enough time, to get some service
2441 // infos from the network.
2442 if (newCollectorCreated)
2443 {
2444 try
2445 {
2446 Thread.sleep(200);
2447 }
2448 catch (InterruptedException e)
2449 {
2450 }
2451 }
2452
2453 return collector.list();
2454 }
2455
2456 /**
2457 * This method disposes all ServiceCollector instances which have been
2458 * created by calls to method <code>list(type)</code>.
2459 *
2460 * @see #list
2461 */
2462 private void disposeServiceCollectors()
2463 {
2464 logger.finer("disposeServiceCollectors()");
2465 synchronized (serviceCollectors)
2466 {
2467 for (Iterator i = serviceCollectors.values().iterator(); i.hasNext();)
2468 {
2469 ServiceCollector collector = (ServiceCollector) i.next();
2470 removeServiceListener(collector.type, collector);
2471 }
2472 serviceCollectors.clear();
2473 }
2474 }
2475
2476 /**
2477 * Instances of ServiceCollector are used internally to speed up the
2478 * performance of method <code>list(type)</code>.
2479 *
2480 * @see #list
2481 */
2482 private static class ServiceCollector implements ServiceListener
2483 {
2484 private static Logger logger = Logger.getLogger(ServiceCollector.class.toString());
2485 /**
2486 * A set of collected service instance names.
2487 */
2488 private Map infos = Collections.synchronizedMap(new HashMap());
2489
2490 public String type;
2491
2492 public ServiceCollector(String type)
2493 {
2494 this.type = type;
2495 }
2496
2497 /**
2498 * A service has been added.
2499 */
2500 public void serviceAdded(ServiceEvent event)
2501 {
2502 synchronized (infos)
2503 {
2504 event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0);
2505 }
2506 }
2507
2508 /**
2509 * A service has been removed.
2510 */
2511 public void serviceRemoved(ServiceEvent event)
2512 {
2513 synchronized (infos)
2514 {
2515 infos.remove(event.getName());
2516 }
2517 }
2518
2519 /**
2520 * A service hase been resolved. Its details are now available in the
2521 * ServiceInfo record.
2522 */
2523 public void serviceResolved(ServiceEvent event)
2524 {
2525 synchronized (infos)
2526 {
2527 infos.put(event.getName(), event.getInfo());
2528 }
2529 }
2530
2531 /**
2532 * Returns an array of all service infos which have been collected by this
2533 * ServiceCollector.
2534 */
2535 public ServiceInfo[] list()
2536 {
2537 synchronized (infos)
2538 {
2539 return (ServiceInfo[]) infos.values().toArray(new ServiceInfo[infos.size()]);
2540 }
2541 }
2542
2543 public String toString()
2544 {
2545 StringBuffer aLog = new StringBuffer();
2546 synchronized (infos)
2547 {
2548 for (Iterator k = infos.keySet().iterator(); k.hasNext();)
2549 {
2550 Object key = k.next();
2551 aLog.append("\n\t\tService: " + key + ": " + infos.get(key));
2552 }
2553 }
2554 return aLog.toString();
2555 }
2556 };
2557
2558 private static String toUnqualifiedName(String type, String qualifiedName)
2559 {
2560 if (qualifiedName.endsWith(type))
2561 {
2562 return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1);
2563 }
2564 else
2565 {
2566 return qualifiedName;
2567 }
2568 }
2569 }
2570