1 /* -*- Mode: Java; tab-width: 4 -*- 2 * 3 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. 4 * 5 * Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. 6 * ("Apple") in consideration of your agreement to the following terms, and your 7 * use, installation, modification or redistribution of this Apple software 8 * constitutes acceptance of these terms. If you do not agree with these terms, 9 * please do not use, install, modify or redistribute this Apple software. 10 * 11 * In consideration of your agreement to abide by the following terms, and subject 12 * to these terms, Apple grants you a personal, non-exclusive license, under Apple's 13 * copyrights in this original Apple software (the "Apple Software"), to use, 14 * reproduce, modify and redistribute the Apple Software, with or without 15 * modifications, in source and/or binary forms; provided that if you redistribute 16 * the Apple Software in its entirety and without modifications, you must retain 17 * this notice and the following text and disclaimers in all such redistributions of 18 * the Apple Software. Neither the name, trademarks, service marks or logos of 19 * Apple Computer, Inc. may be used to endorse or promote products derived from the 20 * Apple Software without specific prior written permission from Apple. Except as 21 * expressly stated in this notice, no other rights or licenses, express or implied, 22 * are granted by Apple herein, including but not limited to any patent rights that 23 * may be infringed by your derivative works or by other works in which the Apple 24 * Software may be incorporated. 25 * 26 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 27 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 28 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 30 * COMBINATION WITH YOUR PRODUCTS. 31 * 32 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 34 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 * ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION 36 * OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT 37 * (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN 38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 40 BrowserApp demonstrates how to use DNS-SD to browse for and resolve services. 41 42 To do: 43 - display resolved TXTRecord 44 */ 45 46 47 import java.awt.*; 48 import java.awt.event.*; 49 import java.util.*; 50 import java.text.*; 51 import javax.swing.*; 52 import javax.swing.event.*; 53 54 import com.apple.dnssd.*; 55 56 57 class BrowserApp implements ListSelectionListener, ResolveListener, Runnable 58 { 59 static BrowserApp app; 60 JFrame frame; 61 DomainListModel domainList; 62 BrowserListModel servicesList, serviceList; 63 JList domainPane, servicesPane, servicePane; 64 DNSSDService servicesBrowser, serviceBrowser, domainBrowser; 65 JLabel hostLabel, portLabel; 66 String hostNameForUpdate; 67 int portForUpdate; 68 69 public BrowserApp() 70 { 71 frame = new JFrame("DNS-SD Service Browser"); 72 frame.addWindowListener(new WindowAdapter() { 73 public void windowClosing(WindowEvent e) {System.exit(0);} 74 }); 75 76 domainList = new DomainListModel(); 77 servicesList = new ServicesBrowserListModel(); 78 serviceList = new BrowserListModel(); 79 80 try { 81 domainBrowser = DNSSD.enumerateDomains( DNSSD.BROWSE_DOMAINS, 0, domainList); 82 83 servicesBrowser = DNSSD.browse( 0, 0, "_services._dns-sd._udp.", "", servicesList); 84 serviceBrowser = null; 85 } 86 catch ( Exception ex) { terminateWithException( ex); } 87 88 this.setupSubPanes( frame.getContentPane()); 89 frame.pack(); 90 frame.setVisible(true); 91 } 92 93 protected void setupSubPanes( Container parent) 94 { 95 parent.setLayout( new BoxLayout( parent, BoxLayout.Y_AXIS)); 96 97 JPanel browserRow = new JPanel(); 98 browserRow.setLayout( new BoxLayout( browserRow, BoxLayout.X_AXIS)); 99 domainPane = new JList( domainList); 100 domainPane.addListSelectionListener( this); 101 JScrollPane domainScroller = new JScrollPane( domainPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 102 browserRow.add( domainScroller); 103 servicesPane = new JList( servicesList); 104 servicesPane.addListSelectionListener( this); 105 JScrollPane servicesScroller = new JScrollPane( servicesPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 106 browserRow.add( servicesScroller); 107 servicePane = new JList( serviceList); 108 servicePane.addListSelectionListener( this); 109 JScrollPane serviceScroller = new JScrollPane( servicePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 110 browserRow.add( serviceScroller); 111 112 /* 113 JPanel buttonRow = new JPanel(); 114 buttonRow.setLayout( new BoxLayout( buttonRow, BoxLayout.X_AXIS)); 115 buttonRow.add( Box.createHorizontalGlue()); 116 JButton connectButton = new JButton( "Don't Connect"); 117 buttonRow.add( connectButton); 118 buttonRow.add( Box.createRigidArea( new Dimension( 16, 0))); 119 */ 120 121 JPanel labelRow = new JPanel(); 122 labelRow.setLayout( new BoxLayout( labelRow, BoxLayout.X_AXIS)); 123 labelRow.add( new JLabel( " Host: ")); 124 hostLabel = new JLabel(); 125 labelRow.add( hostLabel); 126 labelRow.add( Box.createRigidArea( new Dimension( 32, 0))); 127 labelRow.add( new JLabel( "Port: ")); 128 portLabel = new JLabel(); 129 labelRow.add( portLabel); 130 labelRow.add( Box.createHorizontalGlue()); 131 132 parent.add( browserRow); 133 parent.add( Box.createRigidArea( new Dimension( 0, 8))); 134 parent.add( labelRow); 135 // parent.add( buttonRow); 136 parent.add( Box.createRigidArea( new Dimension( 0, 16))); 137 } 138 139 public void valueChanged( ListSelectionEvent e) 140 { 141 try { 142 if ( e.getSource() == domainPane && !e.getValueIsAdjusting()) 143 { 144 int newSel = domainPane.getSelectedIndex(); 145 if ( -1 != newSel) 146 { 147 if ( serviceBrowser != null) 148 serviceBrowser.stop(); 149 serviceList.removeAllElements(); 150 servicesBrowser = DNSSD.browse( 0, 0, "_services._dns-sd._udp.", "", servicesList); 151 } 152 } 153 else if ( e.getSource() == servicesPane && !e.getValueIsAdjusting()) 154 { 155 int newSel = servicesPane.getSelectedIndex(); 156 if ( serviceBrowser != null) 157 serviceBrowser.stop(); 158 serviceList.removeAllElements(); 159 if ( -1 != newSel) 160 serviceBrowser = DNSSD.browse( 0, 0, servicesList.getNthRegType( newSel), "", serviceList); 161 } 162 else if ( e.getSource() == servicePane && !e.getValueIsAdjusting()) 163 { 164 int newSel = servicePane.getSelectedIndex(); 165 166 hostLabel.setText( ""); 167 portLabel.setText( ""); 168 169 if ( -1 != newSel) 170 { 171 DNSSD.resolve( 0, serviceList.getNthInterface( newSel), 172 serviceList.getNthServiceName( newSel), 173 serviceList.getNthRegType( newSel), 174 serviceList.getNthDomain( newSel), 175 this); 176 } 177 } 178 } 179 catch ( Exception ex) { terminateWithException( ex); } 180 } 181 182 public void run() 183 { 184 hostLabel.setText( hostNameForUpdate); 185 portLabel.setText( String.valueOf( portForUpdate)); 186 } 187 188 public void serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, 189 String hostName, int port, TXTRecord txtRecord) 190 { 191 // We want to update GUI on the AWT event dispatching thread, but we can't stop 192 // the resolve from that thread, since stop() is synchronized with this callback. 193 // So, we stop the resolve on this thread, then invokeAndWait on the AWT event thread. 194 195 resolver.stop(); 196 197 hostNameForUpdate = hostName; 198 portForUpdate = port; 199 200 try { 201 SwingUtilities.invokeAndWait(this); 202 } 203 catch ( Exception e) 204 { 205 e.printStackTrace(); 206 } 207 } 208 209 public void operationFailed( DNSSDService service, int errorCode) 210 { 211 service.stop(); 212 // handle failure here 213 } 214 215 protected static void terminateWithException( Exception e) 216 { 217 e.printStackTrace(); 218 System.exit( -1); 219 } 220 221 public static void main(String s[]) 222 { 223 app = new BrowserApp(); 224 } 225 } 226 227 228 class BrowserListModel extends DefaultListModel implements BrowseListener, Runnable 229 { 230 public BrowserListModel() 231 { 232 addCache = new Vector(); 233 removeCache = new Vector(); 234 } 235 236 /* The Browser invokes this callback when a service is discovered. */ 237 public void serviceFound( DNSSDService browser, int flags, int ifIndex, 238 String serviceName, String regType, String domain) 239 { 240 addCache.add( new BrowserListElem( serviceName, domain, regType, ifIndex)); 241 if ( ( flags & DNSSD.MORE_COMING) == 0) 242 this.scheduleOnEventThread(); 243 } 244 245 public void serviceLost( DNSSDService browser, int flags, int ifIndex, 246 String serviceName, String regType, String domain) 247 { 248 removeCache.add( serviceName); 249 if ( ( flags & DNSSD.MORE_COMING) == 0) 250 this.scheduleOnEventThread(); 251 } 252 253 public void run() 254 { 255 while ( removeCache.size() > 0) 256 { 257 String serviceName = (String) removeCache.remove( removeCache.size() - 1); 258 int matchInd = this.findMatching( serviceName); // probably doesn't handle near-duplicates well. 259 if ( matchInd != -1) 260 this.removeElementAt( matchInd); 261 } 262 while ( addCache.size() > 0) 263 { 264 BrowserListElem elem = (BrowserListElem) addCache.remove( addCache.size() - 1); 265 if ( -1 == this.findMatching( elem.fServiceName)) // probably doesn't handle near-duplicates well. 266 this.addInSortOrder( elem); 267 } 268 } 269 270 public void operationFailed( DNSSDService service, int errorCode) 271 { 272 // handle failure here 273 } 274 275 /* The list contains BrowserListElem's */ 276 class BrowserListElem 277 { 278 public BrowserListElem( String serviceName, String domain, String type, int ifIndex) 279 { fServiceName = serviceName; fDomain = domain; fType = type; fInt = ifIndex; } 280 281 public String toString() { return fServiceName; } 282 283 public String fServiceName, fDomain, fType; 284 public int fInt; 285 } 286 287 public String getNthServiceName( int n) 288 { 289 BrowserListElem sel = (BrowserListElem) this.get( n); 290 return sel.fServiceName; 291 } 292 293 public String getNthRegType( int n) 294 { 295 BrowserListElem sel = (BrowserListElem) this.get( n); 296 return sel.fType; 297 } 298 299 public String getNthDomain( int n) 300 { 301 BrowserListElem sel = (BrowserListElem) this.get( n); 302 return sel.fDomain; 303 } 304 305 public int getNthInterface( int n) 306 { 307 BrowserListElem sel = (BrowserListElem) this.get( n); 308 return sel.fInt; 309 } 310 311 protected void addInSortOrder( Object obj) 312 { 313 int i; 314 for ( i = 0; i < this.size(); i++) 315 if ( sCollator.compare( obj.toString(), this.getElementAt( i).toString()) < 0) 316 break; 317 this.add( i, obj); 318 } 319 320 protected int findMatching( String match) 321 { 322 for ( int i = 0; i < this.size(); i++) 323 if ( match.equals( this.getElementAt( i).toString())) 324 return i; 325 return -1; 326 } 327 328 protected void scheduleOnEventThread() 329 { 330 try { 331 SwingUtilities.invokeAndWait( this); 332 } 333 catch ( Exception e) 334 { 335 e.printStackTrace(); 336 } 337 } 338 339 protected Vector removeCache; // list of serviceNames to remove 340 protected Vector addCache; // list of BrowserListElem's to add 341 342 protected static Collator sCollator; 343 344 static // Initialize our static variables 345 { 346 sCollator = Collator.getInstance(); 347 sCollator.setStrength( Collator.PRIMARY); 348 } 349 } 350 351 352 class ServicesBrowserListModel extends BrowserListModel 353 { 354 /* The Browser invokes this callback when a service is discovered. */ 355 public void serviceFound( DNSSDService browser, int flags, int ifIndex, 356 String serviceName, String regType, String domain) 357 // Overridden to stuff serviceName into regType and make serviceName human-readable. 358 { 359 regType = serviceName + ( regType.startsWith( "_udp.") ? "._udp." : "._tcp."); 360 super.serviceFound( browser, flags, ifIndex, this.mapTypeToName( serviceName), regType, domain); 361 } 362 363 public void serviceLost( DNSSDService browser, int flags, int ifIndex, 364 String serviceName, String regType, String domain) 365 // Overridden to make serviceName human-readable. 366 { 367 super.serviceLost( browser, flags, ifIndex, this.mapTypeToName( serviceName), regType, domain); 368 } 369 370 protected String mapTypeToName( String type) 371 // Convert a registration type into a human-readable string. Returns original string on no-match. 372 { 373 final String[] namedServices = { 374 "_afpovertcp", "Apple File Sharing", 375 "_http", "World Wide Web servers", 376 "_daap", "Digital Audio Access", 377 "_apple-sasl", "Apple Password Servers", 378 "_distcc", "Distributed Compiler nodes", 379 "_finger", "Finger servers", 380 "_ichat", "iChat clients", 381 "_presence", "iChat AV clients", 382 "_ssh", "SSH servers", 383 "_telnet", "Telnet servers", 384 "_workstation", "Macintosh Manager clients", 385 "_bootps", "BootP servers", 386 "_xserveraid", "XServe RAID devices", 387 "_eppc", "Remote AppleEvents", 388 "_ftp", "FTP services", 389 "_tftp", "TFTP services" 390 }; 391 392 for ( int i = 0; i < namedServices.length; i+=2) 393 if ( namedServices[i].equals( type)) 394 return namedServices[i + 1]; 395 return type; 396 } 397 } 398 399 400 class DomainListModel extends DefaultListModel implements DomainListener 401 { 402 /* Called when a domain is discovered. */ 403 public void domainFound( DNSSDService domainEnum, int flags, int ifIndex, String domain) 404 { 405 if ( !this.contains( domain)) 406 this.addElement( domain); 407 } 408 409 public void domainLost( DNSSDService domainEnum, int flags, int ifIndex, String domain) 410 { 411 if ( this.contains( domain)) 412 this.removeElement( domain); 413 } 414 415 public void operationFailed( DNSSDService service, int errorCode) 416 { 417 // handle failure here 418 } 419 } 420 421