noel 2004/03/29 16:37:46
Modified: src/java/org/apache/james/dnsserver DNSServer.java
Log:
Fix JAMES-236. This was a newly introduced problem. The iterator's hasNext() needs
to do the work previously deferred to next(), so that we know next() will have a valid
return value.
Revision Changes Path
1.25 +182 -12 james-server/src/java/org/apache/james/dnsserver/DNSServer.java
Index: DNSServer.java
===================================================================
RCS file: /home/cvs/james-server/src/java/org/apache/james/dnsserver/DNSServer.java,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -r1.24 -r1.25
--- DNSServer.java 19 Feb 2004 10:12:35 -0000 1.24
+++ DNSServer.java 30 Mar 2004 00:37:46 -0000 1.25
@@ -29,6 +29,7 @@
import org.xbill.DNS.FindServer;
import org.xbill.DNS.Message;
import org.xbill.DNS.MXRecord;
+import org.xbill.DNS.ARecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
@@ -42,7 +43,7 @@
import java.net.UnknownHostException;
import java.util.*;
-/**
+/*
* Provides DNS client functionality to services running
* inside James
*/
@@ -51,34 +52,34 @@
implements Configurable, Initializable,
org.apache.james.services.DNSServer, DNSServerMBean {
- /**
+ /*
* A resolver instance used to retrieve DNS records. This
* is a reference to a third party library object.
*/
private Resolver resolver;
- /**
+ /*
* A TTL cache of results received from the DNS server. This
* is a reference to a third party library object.
*/
private Cache cache;
- /**
+ /*
* Whether the DNS response is required to be authoritative
*/
private byte dnsCredibility;
- /**
+ /*
* The DNS servers to be used by this service
*/
private List dnsServers = new ArrayList();
- /**
+ /*
* The MX Comparator used in the MX sort.
*/
private Comparator mxComparator = new MXRecordComparator();
- /**
+ /*
* @see
org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
*/
public void configure( final Configuration configuration )
@@ -119,7 +120,7 @@
dnsCredibility = authoritative ? Credibility.AUTH_ANSWER :
Credibility.NONAUTH_ANSWER;
}
- /**
+ /*
* @see org.apache.avalon.framework.activity.Initializable#initialize()
*/
public void initialize()
@@ -157,7 +158,7 @@
getLogger().debug("DNSServer ...init end");
}
- /**
+ /*
* <p>Return the list of DNS servers in use by this service</p>
*
* @return an array of DNS server names
@@ -166,7 +167,7 @@
return (String[])dnsServers.toArray(new String[0]);
}
- /**
+ /*
* <p>Return a prioritized unmodifiable list of MX records
* obtained from the server.</p>
*
@@ -222,7 +223,7 @@
}
}
- /**
+ /*
* Looks up DNS records of the specified type for the specified name.
*
* This method is a public wrapper for the private implementation
@@ -235,7 +236,7 @@
return rawDNSLookup(name,false,type);
}
- /**
+ /*
* Looks up DNS records of the specified type for the specified name
*
* @param namestr the name of the host to be looked up
@@ -346,6 +347,175 @@
int pa = ((MXRecord)a).getPriority();
int pb = ((MXRecord)b).getPriority();
return (pa == pb) ? (512 - random.nextInt(1024)) : pa - pb;
+ }
+ }
+
+ /*
+ * Returns an Iterator over org.apache.mailet.HostAddress, a
+ * specialized subclass of javax.mail.URLName, which provides
+ * location information for servers that are specified as mail
+ * handlers for the given hostname. This is done using MX records,
+ * and the HostAddress instances are returned sorted by MX priority.
+ * If no host is found for domainName, the Iterator returned will be
+ * empty and the first call to hasNext() will return false. The
+ * Iterator is a nested iterator: the outer iteration is over the
+ * results of the MX record lookup, and the inner iteration is over
+ * potentially multiple A records for each MX record. DNS lookups
+ * are deferred until actually needed.
+ *
+ * @since v2.2.0a16-unstable
+ * @param domainName - the domain for which to find mail servers
+ * @return an Iterator over HostAddress instances, sorted by priority
+ */
+ public Iterator getSMTPHostAddresses(final String domainName) {
+ return new Iterator() {
+ private Iterator mxHosts = new MxSorter(domainName);
+ private Iterator addresses = null;
+
+ public boolean hasNext() {
+ /* Make sure that when next() is called, that we can
+ * provide a HostAddress. This means that we need to
+ * have an inner iterator, and verify that it has
+ * addresses. We could, for example, run into a
+ * situation where the next mxHost didn't have any valid
+ * addresses.
+ */
+ if ((addresses == null || !addresses.hasNext()) &&
mxHosts.hasNext()) do {
+ final String nextHostname = (String)mxHosts.next();
+ addresses = new Iterator() {
+ private Record[] aRecords = lookup(nextHostname, Type.A);
+ int i = 0;
+
+ public boolean hasNext() {
+ return aRecords != null && i < aRecords.length;
+ }
+
+ public Object next() {
+ return new org.apache.mailet.HostAddress(nextHostname,
"smtp://" + ((ARecord)aRecords[i++]).getAddress().getHostAddress());
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException ("remove not
supported by this iterator");
+ }
+ };
+ } while (!addresses.hasNext() && mxHosts.hasNext());
+
+ return addresses != null && addresses.hasNext();
+ }
+
+ public Object next() {
+ return addresses != null ? addresses.next() : null;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException ("remove not supported by
this iterator");
+ }
+ };
+ }
+
+ /* A way to get mail hosts to try. If any MX hosts are found for the
+ * domain name with which this is constructed, then these MX hostnames
+ * are returned in priority sorted order, lowest priority numbers coming
+ * first. And, whenever multiple hosts have the same priority then these
+ * are returned in a randomized order within that priority group, as
+ * specified in RFC 2821, Section 5.
+ *
+ * If no MX hosts are found for the domain name, then a DNS search is
+ * performed for an A record. If an A record is found then domainName itself
+ * will be returned by the Iterator, and it will be the only object in
+ * the Iterator. If however no A record is found (in addition to no MX
+ * record) then the Iterator constructed will be empty; the first call to
+ * its hasNext() will return false.
+ *
+ * This behavior attempts to satisfy the requirements of RFC 2821, Section 5.
+ * @since v2.2.0a16-unstable
+ */
+ private class MxSorter implements Iterator {
+ private int priorListPriority = Integer.MIN_VALUE;
+ private ArrayList equiPriorityList = new ArrayList();
+ private Record[] mxRecords;
+ private Random rnd = new Random ();
+
+ /* The implementation of this class attempts to achieve efficiency by
+ * performing no more sorting of the rawMxRecords than necessary. In the
+ * large majority of cases the first attempt, made by a client of this class
+ * to connect to an SMTP server for a given domain, will succeed. As such,
+ * in most cases only one call will be made to this Iterator's
+ * next(), and in that majority of cases there will have been no need
+ * to sort the array of MX Records. This implementation would, however, be
+ * relatively inefficient in the case where all hosts fail, when every
+ * Object is called out of a long Iterator.
+ */
+
+ private MxSorter(String domainName) {
+ mxRecords = lookup(domainName, Type.MX);
+ if (mxRecords == null || mxRecords.length == 0) {
+ //no MX records were found, so try to use the domainName
+ Record[] aRecords = lookup(domainName, Type.A);
+ if(aRecords != null && aRecords.length > 0) {
+ equiPriorityList.add(domainName);
+ }
+ }
+ }
+
+ /*
+ * Sets presentPriorityList to contain all hosts
+ * which have the least priority greater than pastPriority.
+ * When this is called, both (rawMxRecords.length > 0) and
+ * (presentPriorityList.size() == 0), by contract.
+ * In the case where this is called repeatedly, so that priorListPriority
+ * has already become the highest of the priorities in the rawMxRecords,
+ * then this returns without having added any elements to
+ * presentPriorityList; presentPriorityList.size remains zero.
+ */
+ private void createPriorityList(){
+ int leastPriorityFound = Integer.MAX_VALUE;
+ /* We loop once through the rawMxRecords, finding the lowest priority
+ * greater than priorListPriority, and collecting all the hostnames
+ * with that priority into equiPriorityList.
+ */
+ for (int i = 0; i < mxRecords.length; i++) {
+ MXRecord thisRecord = (MXRecord)mxRecords[i];
+ int thisRecordPriority = thisRecord.getPriority();
+ if (thisRecordPriority > priorListPriority) {
+ if (thisRecordPriority < leastPriorityFound) {
+ equiPriorityList.clear();
+ leastPriorityFound = thisRecordPriority;
+ equiPriorityList.add(thisRecord.getTarget().toString());
+ } else if (thisRecordPriority == leastPriorityFound) {
+ equiPriorityList.add(thisRecord.getTarget().toString());
+ }
+ }
+ }
+ priorListPriority = leastPriorityFound;
+ }
+
+ public boolean hasNext(){
+ if (equiPriorityList.size() > 0){
+ return true;
+ }else if (mxRecords != null && mxRecords.length > 0){
+ createPriorityList();
+ return equiPriorityList.size() > 0;
+ } else{
+ return false;
+ }
+ }
+
+ public Object next(){
+ if (hasNext()){
+ /* this randomization is done to comply with RFC-2821 */
+ /* Note: java.util.Random.nextInt(limit) is about twice as fast as
(int)(Math.random()*limit) */
+ int getIndex = rnd.nextInt(equiPriorityList.size());
+ Object returnElement = equiPriorityList.get(getIndex);
+ equiPriorityList.remove(getIndex);
+ return returnElement;
+ }else{
+ throw new NoSuchElementException();
+ }
+ }
+
+ public void remove () {
+ throw new UnsupportedOperationException ("remove not supported by this
iterator");
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]