In the mean time I am happy to report the DNS querying code that I posted in A Breakthrough! has graduated from the Whiteboard and found it's own home. It's been fully tricked out with hostname resolution caching and all. There are a few concurrency related goodies in the code that I want to talk about to those readers still trying to figure out what the java.util.concurrency packages are good for. Since the code is 183 lines long (with comments) I'm splitting its discussion into two entries. The first entry is the one you are reading now and it contains the source code. The second entry will discuss the concurrency goodies and I'll post that sometime later today or maybe tomorrow.
Here is the code, enjoy:(Updated 13 Feb 06, 10:14)
6 import javax.naming.CommunicationException; 7 import javax.naming.NameNotFoundException; 8 import javax.naming.NamingException; 9 import javax.naming.ServiceUnavailableException; 10 11 import javax.naming.directory.InitialDirContext; 12 13 import java.net.InetAddress; 14 import java.net.PortUnreachableException; 15 import java.net.UnknownHostException; 16 17 import java.util.Enumeration; 18 import java.util.Queue; 19 import java.util.TreeSet; 20 21 import java.util.concurrent.ConcurrentHashMap; 22 import java.util.concurrent.ConcurrentLinkedQueue; 23 import java.util.concurrent.ConcurrentMap; 24 import java.util.concurrent.CountDownLatch; 25 26 /** 27 * Lookup service for DNS MX records. 28 * <p> 29 * <b>Thread safe:</b> yes 30 * </p> 31 * 32 * @author HashiDiKo</a> 33 */ 34 public final class MXRecordQueryService 35 { 36 private static final String URL = "dns:/"; 37 38 private static final String MXATT = "MX"; 39 40 private static final String[] MXFILTER = {MXATT}; 41 42 private static final MXRecordQueryService THIS = new MXRecordQueryService(); 43 44 /** 45 * Cache queries. 46 * @see MXRecordQueryService.CacheEntry 47 */ 48 private final ConcurrentMap<String, CacheEntry> cache = new ConcurrentHashMap<String, CacheEntry>(); 49 50 /** 51 * Provides a mechanism for ensuring that no 2 (or more) threads will race to query the same domain but at the same 52 * time allow concurrent queries of different domains. 53 */ 54 private final ConcurrentMap<String, CountDownLatch> domainLocks = new ConcurrentHashMap<String, CountDownLatch>(); 55 56 private MXRecordQueryService() 57 { 58 } 59 60 public static MXRecordQueryService getInstance() 61 { 62 return THIS; 63 } 64 65 /** 66 * Returns the IP addresses of the MX records for <tt>domain</tt> sorted by preference or <tt>null</tt> if no valid 67 * hostnames exits for the MX records. 68 * 69 * @param domain The domain name. 70 * 71 * @return The IP addresses of the MX records for <tt>domain</tt> sorted by preference or <tt>null</tt> if no valid 72 * hostnames exits for the MX records. 73 * 74 * @throws NameNotFoundException Indicates that the <tt>domain</tt> is invalid. 75 * @throws ServiceUnavailableException Indicates that <tt>domain</tt> is valid but no MX records exists for it. 76 * @throws CommunicationException Indicates a problem contacting the configured DNS server(s). 77 * @throws RuntimeException If some other <tt>javax.naming.NamingException</tt> occurs. 78 */ 79 public Queue<String> get( String domain ) 80 throws ServiceUnavailableException, NameNotFoundException, CommunicationException, InterruptedException 81 { 82 CacheEntry cached = cache.get( domain ); 83 if( null != cached ) 84 { 85 if( System.currentTimeMillis() <= cached.expiration ) 86 return cached.object; 87 cache.remove( domain, cached ); 88 } 89 90 CountDownLatch lock = domainLocks.get( domain ); 91 if( null == lock ) 92 { 93 lock = new CountDownLatch( 1 ); 94 CountDownLatch prempted = domainLocks.putIfAbsent( domain, lock ); 95 if( null == prempted ) 96 { /* Take another look because we may have preempted and it's cheaper to look than to lookup. */ 97 cached = cache.get( domain ); 98 if( null != cached && System.currentTimeMillis() <= cached.expiration ) 99 return cached.object; 100 101 try 102 { 103 TreeSet<MXRecord> sorter = new TreeSet<MXRecord>(); 104 String url = URL.concat( domain ); 105 for( Enumeration en = new InitialDirContext().getAttributes( url, MXFILTER ).get( MXATT ).getAll(); 106 en.hasMoreElements(); ) 107 sorter.add( new MXRecord( (String)en.nextElement() ) ); 108 109 Queue<String> queue = new ConcurrentLinkedQueue<String>(); 110 for( MXRecord mx : sorter ) 111 { 112 try 113 { 114 for( InetAddress addr : InetAddress.getAllByName( mx.hostname ) ) 115 queue.offer( addr.getHostAddress() ); 116 } 117 catch( UnknownHostException ignore ) 118 { 119 } 120 } 121 cache.put( domain, new CacheEntry( queue ) ); 122 return queue; 123 } 124 catch( NameNotFoundException nex ) 125 { 126 throw nex; 127 } 128 catch( ServiceUnavailableException sux ) 129 { 130 throw sux; 131 } 132 catch( CommunicationException comx ) 133 { 134 if( comx.getRootCause() instanceof PortUnreachableException ) 135 throw comx; 136 throw new RuntimeException( comx ); 137 } 138 catch( NamingException nex ) 139 { 140 throw new RuntimeException( nex ); 141 } 142 finally 143 { 144 lock.countDown(); 145 boolean invariant = domainLocks.remove( domain, lock ); 146 assert invariant : "Impossible!"; 147 } 148 } 149 else 150 lock = prempted; 151 } 152 lock.await(); 153 return get( domain ); 154 } 155 156 private static final class CacheEntry 157 { 158 final Queue<String> object; 159 160 final long expiration; 161 162 CacheEntry( Queue<String> object ) 163 { 164 this.object = object; 165 expiration = System.currentTimeMillis() + 60000; 166 } 167 } 168 169 /** 170 * Utility class for extracting the different parts of a MX record string. 171 */ 172 private static final class MXRecord implements Comparable<MXRecord> 173 { 174 final String hostname; 175 176 private final int pref; 177 178 MXRecord( String mx ) 179 { 180 assert mx.matches( "\\d++\\s++.+\\." ) : "Invalid MX record format: ".concat( mx ); 181 182 int offset = 0; 183 int len = mx.length() - 1;// Strip trailing dot. 184 185 /* Extract preference integer. */ 186 while( offset < len && Character.isDigit( mx.charAt( offset ) ) ) 187 offset++; 188 pref = Integer.parseInt( mx.substring( 0, offset ) ); 189 190 /* Extract domain name. */ 191 while( offset < len && Character.isWhitespace( mx.charAt( offset ) ) ) 192 offset++; 193 194 hostname = mx.substring( offset, len ); 195 } 196 197 public int compareTo( MXRecord o ) 198 { 199 int diff = pref - o.pref; 200 return 0 == diff ? hostname.compareTo( o.hostname ) : diff; 201 } 202 } 203 } 204
No comments:
Post a Comment