harmony-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From telli...@apache.org
Subject svn commit: r410240 [3/4] - in /incubator/harmony/enhanced/classlib/trunk/modules/jndi: make/ make/common/ src/main/java/org/apache/harmony/jndi/provider/ src/main/java/org/apache/harmony/jndi/provider/dns/ src/main/java/org/apache/harmony/util/ src/ma...
Date Tue, 30 May 2006 11:25:01 GMT
Added: incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/Resolver.java
URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/Resolver.java?rev=410240&view=auto
==============================================================================
--- incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/Resolver.java (added)
+++ incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/Resolver.java Tue May 30 04:24:59 2006
@@ -0,0 +1,1575 @@
+/*
+ * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @author Alexei Y. Zakharov
+ * @version $Revision: 1.1.2.5 $
+ */
+
+package org.apache.harmony.jndi.provider.dns;
+
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.logging.Level;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.ServiceUnavailableException;
+
+import org.apache.harmony.util.logging.LogConst;
+
+/**
+ * This class implements the functionality of a simple DNS resolver.<br> 
+ * Following DNS Resource Records are supported:
+ * <ul>
+ * <li>A</li>
+ * <li>NS</li>
+ * <li>CNAME</li>
+ * <li>SOA</li>
+ * <li>PTR</li>
+ * <li>MX</li>
+ * <li>TXT</li>
+ * <li>HINFO</li>
+ * <li>AAAA (TODO)</li>
+ * <li>NAPTR (TODO)</li>
+ * <li>SRV</li>
+ * </ul>
+ * Following DNS classes are supported:
+ * <ul>
+ * <li>IN</li>
+ * <li>HS (TODO)</li>
+ * </ul>
+ * <br>
+ * @author Alexei Zakharov
+ * @version $Revision: 1.1.2.5 $
+ * TODO do we need broadcasting and IP multicasting to obtain initial name
+ * server address?
+ * TODO network-preference feature for choosing most welcome address 
+ * for multihomed hosts (RFC 1123 point 6.1.3.4)
+ * TODO add general IPv6 support and support for AAAA resource record 
+ */
+public class Resolver implements Runnable {
+    
+    private static final int MSG_MAX_BYTES = 512;
+    private static final int MAX_THREADS = 5;
+
+    /**
+     * Entry of the resolver thread list.
+     * @author Alexei Zakharov
+     */
+    private static class ThreadListEntry {
+        Thread thread;
+        String serverNameToResolve;
+        int dnsClass;
+    }
+
+    
+    private static Random rndGen = new Random();
+
+    // resolver configuration
+    private int initialTimeout;
+    private int timeoutRetries;
+    private boolean authoritativeAnswerDesired;
+    private boolean recursionDesired;
+    // maximum number of active threads
+    private int threadNumberLimit;
+    
+    // vector with currently running Resolver threads 
+    private Vector resolverThreads = new Vector();
+    // the list of host names that should be resolved
+    private Vector hostnamesToResolve = new Vector();
+    // semaphore that controls access to both lists above
+    private Object threadListSemaphore = new Object();
+    
+    // <---- Constructor's section
+    
+    /**
+     * Constructs a <code>Resolver</code> object with default initial
+     * timeout (1 second), default timeout retries (4 times), default 
+     * recursion desired switch (true) and default authoritative answer
+     * desired 
+     * @see #Resolver(int, int)
+     */
+    public Resolver() {
+        this(ProviderConstants.DEFAULT_INITIAL_TIMEOUT,
+                ProviderConstants.DEFAULT_TIMEOUT_RETRIES,
+                ProviderConstants.DEFAULT_MAX_THREADS,
+                ProviderConstants.DEFAULT_AUTHORITATIVE,
+                ProviderConstants.DEFAULT_RECURSION);
+    }
+    
+    /**
+     * Constructs a <code>Resolver</code> object with given initial
+     * timeout and timeout retries. Initially the resolver will try to access
+     * DNS servers with timeout set to initial timeout. If none of servers
+     * answer it will double the timeout and perform the second round. The
+     * process will continue for <code>timeoutRetries</code> rounds. If there
+     * is no answer still the resolver will give up.   
+     * 
+     * @param initialTimeout the initial timeout that is used during the
+     *  first round (in milliseconds)
+     * @param timeoutRetries number of rounds the Resolver should perform
+     *  before giving up 
+     * @param authoritativeAnswerDesired do we want to receive only
+     *  authoritative answers
+     * @param recursionDesired do we want our outgoing packages to have
+     * RD but set
+     */
+    public Resolver(int initialTimeout,
+                    int timeoutRetries,
+                    int maxThreads,
+                    boolean authoritativeAnswerDesired,
+                    boolean recursionDesired)
+    {
+        this.initialTimeout = initialTimeout;
+        this.timeoutRetries = timeoutRetries;
+        this.threadNumberLimit = maxThreads;
+        this.authoritativeAnswerDesired = authoritativeAnswerDesired;
+        this.recursionDesired = recursionDesired;
+    }
+
+    /**
+     * @return Returns the threadNumberLimit.
+     */
+    public int getThreadNumberLimit() {
+        return threadNumberLimit;
+    }
+    /**
+     * @param threadNumberLimit The threadNumberLimit to set.
+     */
+    public void setThreadNumberLimit(int threadNumberLimit) {
+        this.threadNumberLimit = threadNumberLimit;
+    }
+    /**
+     * @return Returns the authoritativeAnswerDesired.
+     */
+    public boolean isAuthoritativeAnswerDesired() {
+        return authoritativeAnswerDesired;
+    }
+    /**
+     * @param authoritativeAnswerDesired The authoritativeAnswerDesired to set.
+     */
+    public void setAuthoritativeAnswerDesired(
+            boolean authoritativeAnswerDesired) {
+        this.authoritativeAnswerDesired = authoritativeAnswerDesired;
+    }
+    /**
+     * @return Returns the initialTimeout.
+     */
+    public int getInitialTimeout() {
+        return initialTimeout;
+    }
+    /**
+     * @param initialTimeout The initialTimeout to set.
+     */
+    public void setInitialTimeout(int initialTimeout) {
+        this.initialTimeout = initialTimeout;
+    }
+    /**
+     * @return Returns the recursionDesired.
+     */
+    public boolean isRecursionDesired() {
+        return recursionDesired;
+    }
+    /**
+     * @param recursionDesired The recursionDesired to set.
+     */
+    public void setRecursionDesired(boolean recursionDesired) {
+        this.recursionDesired = recursionDesired;
+    }
+    /**
+     * @return Returns the timeoutRetries.
+     */
+    public int getTimeoutRetries() {
+        return timeoutRetries;
+    }
+    /**
+     * @param timeoutRetries The timeoutRetries to set.
+     */
+    public void setTimeoutRetries(int timeoutRetries) {
+        this.timeoutRetries = timeoutRetries;
+    }
+    
+    // <---- End of constructor's section
+    
+    /**
+     * Checks available name servers if they have any resource records related
+     * to given name & type & class combination. Standard DNS lookup algorithm
+     * is used.  
+     *
+     * @param name well-formed domain name
+     * @param types an array of types; only records that have such types will
+     *  be returned
+     * @param classes
+     * @return enumeration with found resource records
+     * @throws SecurityException if the resolver is not allowed to use a
+     *  network subsystem
+     * @throws NameNotFoundException if authoritative server for desired
+     * zone was contacted but given name has not been found in that zone
+     * @throws ServiceUnavailableException if no authoritative server for
+     * desired name was found or all servers are dead or malfunction   
+     * @throws DomainProtocolException if some DNS specific error has occured 
+     */
+    public Enumeration lookup(String name, int[] types, int[] classes)
+           throws SecurityException,
+                  NameNotFoundException,
+                  ServiceUnavailableException,
+                  DomainProtocolException
+    {
+                
+        // Algorithm:
+        //  1. Set workZone to the parent of qName; clear queriedServers. 
+        //  2. Try to get a complete answer for the workZone from the servers
+        //   currently available in SLIST exclude servers from queriedServers.
+        //  3. update queiriedServers with "visited servers" info.
+        //  4. If the complete answer was received - return it to the user;exit.
+        //  5. If the delegation was received:
+        //    a) If we already have this server & zone pair in SLIST - skip it.
+        //    b) If we don't have - put it into SLIST
+        //    c) If we haven't received any new delegations - goto step (7)
+        //    d) If some new delegation has been received:
+        //      1) from delegations: found the zone with the best matching count
+        //       with qName
+        //      2) if this matching count is bigger than matching count between
+        //       workZone and qName:
+        //        - set workZone to zone with biggest matching count determined
+        //           at step (5.d.1)
+        //        - goto step (2)
+        //      3) if it doesn't then goto step 2 with the same workZone
+        //  6. If ALIAS was received ...
+        //  7. If no answer has been received:
+        //    a) Check if the  workZone is the root zone.
+        //    b) If so - give up; return empty result to the user.
+        //    c) If it isn't, set workZone to parent of workZone. Goto step (2).
+
+        //SList slist = SList.getInstance();
+        ResolverCache cache = ResolverCache.getInstance();
+        
+        Vector questions = new Vector();
+        Vector answers = new Vector(); 
+
+        
+        if (name == null) {
+            throw new NullPointerException("name is null");
+        }
+        if (types == null) {
+            throw new NullPointerException("types is null");
+        }
+        if (classes == null) {
+            throw new NullPointerException("classes is null");
+        }
+        // construct a set of question records
+        for (int i = 0; i < classes.length; i++) {
+            for (int j = 0; j < types.length; j++) {
+                QuestionRecord quest = new QuestionRecord(name, types[j],
+                        classes[i]);
+                questions.addElement(quest);
+            }
+        }
+        // iterate over question records
+        for (int i = 0; i < questions.size(); i++) {
+            QuestionRecord curQuestion =
+                    (QuestionRecord) questions.elementAt(i);
+            String qName = curQuestion.getQName();
+            Message mesToSend = null;
+            Message receivedMes = null;
+            AnalysisReport report = null;
+            String workZone;
+            Hashtable visitedServers = new Hashtable();
+
+            if (LogConst.DEBUG) {
+                ProviderMgr.logger.fine("Current question: " +
+                        curQuestion.toString());
+            }
+            // look in cache
+            if (curQuestion.getQType() != ProviderConstants.ANY_QTYPE &&
+                    curQuestion.getQClass() != ProviderConstants.ANY_QCLASS)
+            {
+                Enumeration recEnum = cache.get(curQuestion);
+
+                if (recEnum.hasMoreElements()) {
+                    while (recEnum.hasMoreElements()) {
+                        answers.addElement(recEnum.nextElement());
+                    }
+                    // we don't need to query any servers since the information
+                    // we want has been found in the local cache
+                    if (LogConst.DEBUG) {
+                        ProviderMgr.logger.fine(
+                                "Information was gathered from cache");
+                    }
+                    continue;
+                }
+            }
+
+            // query remote DNS servers
+
+            // determine work zone 
+            if (qName != null && !qName.equals(".")) {
+                workZone = qName;
+                // support for SRV-style qNames 
+                while (workZone.startsWith("_")) {
+                    workZone = ProviderMgr.getParentName(workZone);
+                }
+            } else {
+                workZone = ".";
+            }
+            if (LogConst.DEBUG) {
+                ProviderMgr.logger.fine("Lookup: new workZone is " +
+                        "\"" + workZone + "\"");
+            }
+            // construct request message
+            try {
+                mesToSend = createMessageForSending(qName,
+                        curQuestion.getQType(), curQuestion.getQClass());
+                if (LogConst.DEBUG) {
+                    ProviderMgr.logger.finest("Message to send:\n" +
+                            mesToSend.toString());
+                }
+
+            } catch (DomainProtocolException e) {
+                throw e;
+            }
+            while (true) {
+                boolean noIdea = false;
+
+                try {
+                    receivedMes = queryServers(mesToSend, workZone,
+                            visitedServers, false);
+                    if (receivedMes != null) {
+                        report = analyzeAnswer(mesToSend, receivedMes);
+                        if (!report.messageWasTruncated) {
+                            // Put all extra records into the cache for
+                            // future use
+                            for (int k = 0; k < report.extraRecords.size();
+                                     k++)
+                            {
+                                ResourceRecord rec = (ResourceRecord)
+                                        report.extraRecords.elementAt(k);
+    
+                                cache.put(rec);
+                            }
+                        }
+                        else {
+                            // Truncated message MUST NOT be cached and later
+                            // used in such a way that the fact that they are
+                            // truncated is lost (RFC 1123 point 6.1.3.2).
+                        }
+                        // examine the report
+                        if (report.completeAnswerWasReceived) {
+                            // complete answer
+                            if (LogConst.DEBUG) {
+                                ProviderMgr.logger.fine(
+                                      "Lookup: a complete answer was received");
+                            }
+                            for (int k = 0; k < report.records.size(); k++) {
+                                ResourceRecord rec = (ResourceRecord)
+                                        report.records.elementAt(k); 
+                                answers.addElement(rec);
+                                // we are sure that the answer section has not
+                                // been truncated so we can put the record
+                                // into the cache
+                                cache.put(rec);
+                            }
+                            // exit the loop
+                            break;
+                        }
+                        else if (report.nameError) {
+                            // name error
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine("Lookup: name error");
+                            }
+                            throw new NameNotFoundException("The name " + name +
+                                    " has not been found");
+                        }
+                        else if (report.aliasInfoWasReceived) {
+                            // alias received
+                            //QuestionRecord newQuestion = new QuestionRecord();
+
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine(
+                                        "Lookup: an alias was received");
+                            }                           
+                            qName = report.newName;
+                            curQuestion.setQName(qName);
+                            // look in cache
+                            if (curQuestion.getQType() !=
+                                        ProviderConstants.ANY_QTYPE &&
+                                curQuestion.getQClass() !=
+                                        ProviderConstants.ANY_QCLASS)
+                            {
+                                Enumeration recEnum = cache.get(curQuestion);
+
+                                if (recEnum.hasMoreElements()) {
+                                    while (recEnum.hasMoreElements()) {
+                                        answers.addElement(
+                                                recEnum.nextElement());
+                                    }
+                                    // We don't need to query any more servers
+                                    // since the information we want has been
+                                    // found in the local cache.
+                                    // Let's switch to next question if any.
+                                    break;
+                                }
+                            }
+                            if (qName != null && !qName.equals("."))
+                            {
+                                workZone = qName;
+                            }
+                            else {
+                                workZone = ".";
+                            }
+                            visitedServers = new Hashtable();
+                            for (int k = 0; k < report.records.size(); k++) {
+                                answers.addElement(report.records.elementAt(k));
+                            }
+                            // construct a new request message
+                            try {
+                                mesToSend = createMessageForSending(qName,
+                                            curQuestion.getQType(),
+                                            curQuestion.getQClass());
+                            } catch (DomainProtocolException e) {
+                                throw e;
+                            }
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine("Lookup: new name is " +
+                                        "\"" + qName + "\"");
+                                ProviderMgr.logger.fine(
+                                        "Lookup: new workZone is " +
+                                        "\"" + workZone + "\"");
+                            }
+                        }
+                        else if (report.delegationArrived) {
+                            // new delegation, probably need to query once again
+                            int k17 = -1;
+                            int matchingCount = ProviderMgr.getMatchingCount(
+                                    qName, workZone);
+
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine(
+                                        "Lookup: delegation arrived");
+                            }
+                            for (int k = 0; k < report.delegationZones.size();
+                                    k++)
+                            {
+                                String curZone = (String)
+                                        report.delegationZones.elementAt(k);
+                                int tmpMatchingCount = ProviderMgr.
+                                        getMatchingCount(qName, curZone);
+    
+                                if (tmpMatchingCount > matchingCount) {
+                                    k17 = k;
+                                    matchingCount = tmpMatchingCount;
+                                }
+                                
+                            }
+                            if (k17 != -1) {
+                                // better delegation was received
+                                workZone = (String)
+                                        report.delegationZones.elementAt(k17);
+                                if (LogConst.DEBUG) {                            
+                                    ProviderMgr.logger.fine(
+                                         "Lookup: better delegation was found");
+                                }
+                            }
+                            else {
+                                // no better delegation
+                                // do nothing, just query the next server of
+                                // the current workZone 
+                            }
+                        }
+                        else {
+                            noIdea = true;
+                        }
+                    } // end of if report != null block
+                    else {
+                        noIdea = true;
+                    }
+                    if (noIdea) {
+                        // Resolver has no idea how to get info about 
+                        // desired host while querying master hosts of the
+                        // current workZone.
+                        // Let's make one step up to the root.
+                        if (LogConst.DEBUG) {                            
+                            ProviderMgr.logger.fine("Lookup: no idea");
+                        }
+                        if (!workZone.equals(".")) {
+                            workZone = ProviderMgr.getParentName(workZone);
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine(
+                                        "Lookup: new work zone is " +
+                                        "\"" + workZone + "\"");
+                            }
+                        }
+                        else {
+                            // give up
+                            break;
+                            //throw new ServiceUnavailableException(
+                            //        "Unable to " +
+                            //        "contact authoritative server for " +
+                            //        qName +
+                            //        " and no other results were found");
+                        }
+                    }
+                }
+                catch (NameNotFoundException e) {
+                    throw e;
+                }
+                catch (DomainProtocolException e) {
+                    throw e;
+                }
+            } // query servers loop
+
+        } // questions loop
+        return answers.elements();
+    }
+
+    /**
+     * Lists entire DNS zone using zone transfer mechanism.
+     * 
+     * @param name DNS zone name
+     * @return enumeration with found <code>ResourceRecord</code> objects
+     * @throws SecurityException if the resolver is not allowed to use a
+     *  network subsystem
+     * @throws NameNotFoundException if authoritative server(s) was not found
+     * @throws ServiceUnavailableException if none of found servers permits zone
+     * transfers   
+     * @throws DomainProtocolException if some DNS specific error has occured 
+     */
+    public Enumeration list(String name) throws NamingException {
+        final int OUT_BUF_SIZE = 512;
+        final int IN_BUF_SIZE = 65536;
+        
+        Vector answerVect = new Vector(); 
+        Message mesToSend = null;
+        Message receivedMes = null;
+        Enumeration enum1;
+        //String zoneMasterServer = null;
+        //Vector authoritativeServerIPs = new Vector();
+        HashSet authoritativeServers = new HashSet();
+        Iterator authServersIter;
+        int qClassArr[] = new int[1];
+        byte outBuf[] = new byte[OUT_BUF_SIZE];
+        int outLen;
+        byte inBuf[] = new byte[IN_BUF_SIZE];
+        boolean received = false;
+        boolean completeAnswer = false;
+        String proto = null;
+
+        ResolverCache cache = ResolverCache.getInstance();
+        //SList slist = SList.getInstance();
+        
+        if (name == null) {
+            throw new NullPointerException("name is null");
+        }
+        // if given name is SRV style name where domain name is prefixed 
+        // with _Proto
+        if (name.startsWith("_")) {
+            int n = name.indexOf('.');
+
+            if (n != -1) {
+                proto = name.substring(0, n);
+                if (name.length() > n) {
+                    name = name.substring(n + 1, name.length());
+                } else {
+                    // nonsense
+                    name = ".";
+                }
+            } else {
+                // nonsense
+                name = ".";
+            }
+        }
+        enum1 = lookup(name, new int[] {ProviderConstants.NS_TYPE}, 
+                new int[] {ProviderConstants.ANY_QTYPE} );
+        mesToSend = createMessageForSending(name,
+                ProviderConstants.AXFR_QTYPE, ProviderConstants.ANY_QCLASS);
+        outLen = mesToSend.writeBytes(outBuf, 0);
+        // determine the list of zone authoritative servers
+        while (enum1.hasMoreElements()) {
+            ResourceRecord rr = (ResourceRecord) enum1.nextElement();
+
+            if (rr.getRRType() == ProviderConstants.NS_TYPE) {
+                authoritativeServers.add((String) rr.getRData());
+                // assertion: all authoritative servers should have the same
+                // DNS class
+                qClassArr[0] = rr.getRRClass();
+            } else if (rr.getRRType() == ProviderConstants.SOA_TYPE) {
+                StringTokenizer st = new StringTokenizer(
+                        (String) rr.getRData(), " ");
+
+                if (st.hasMoreTokens()) {
+                    authoritativeServers.add(st.nextToken());
+                    qClassArr[0] = rr.getRRClass();
+                    break;
+                } else {
+                    ProviderMgr.logger.warning("Invalid SOA record");
+                }
+            }
+        }
+        // try to perform a zone transfer
+        authServersIter = authoritativeServers.iterator();
+      authServersLoop:
+        while (authServersIter.hasNext()) {
+            String authServerName = (String) authServersIter.next();
+            Enumeration addrEnum = lookup(authServerName,
+                    new int[] {ProviderConstants.A_TYPE}, qClassArr);
+            
+            while (addrEnum.hasMoreElements()) {
+                ResourceRecord curRR = (ResourceRecord) addrEnum.nextElement();
+                String ip = (String) curRR.getRData();
+                int n;
+
+                try {
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine(
+                                "Initiating zone transfer, IP=" + ip);
+                    }
+                    n = TransportMgr.sendReceiveTCP(ip,
+                            ProviderConstants.DEFAULT_DNS_PORT,
+                            outBuf, outLen, inBuf, IN_BUF_SIZE,
+                            this.initialTimeout * this.timeoutRetries);
+                    received = true;
+                } catch (SocketTimeoutException e) {
+                    
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Socket timeout");
+                    }
+                } catch (DomainProtocolException e) {
+                    // some problem was encountered
+                    ProviderMgr.logger.log(Level.WARNING,
+                            "Connection failure", e);
+                }
+                if (received) {
+                    receivedMes = new Message();
+                    try {
+                        int rCode;
+    
+                        Message.parseMessage(inBuf, 0, receivedMes);
+                        rCode = receivedMes.getRCode();
+                        switch (rCode) {
+                        case ProviderConstants.NO_ERROR:
+                            // put all received records into Resolver's cache
+                            for (int k = 0; k < 3; k++) {
+                                switch (k) {
+                                case 0:
+                                    enum1 = receivedMes.getAnswerRRs();
+                                    break;
+                                case 1:
+                                    enum1 = receivedMes.getAuthorityRRs();
+                                    break;
+                                case 2:
+                                    enum1 = receivedMes.getAdditionalRRs();
+                                    break;
+                                }
+                                while (enum1.hasMoreElements()) {
+                                    ResourceRecord rr = (ResourceRecord)
+                                            enum1.nextElement();
+    
+                                    cache.put(rr);
+                                    if (k == 0) {
+                                        answerVect.addElement(rr);
+                                    }
+                                }
+                            }
+                            completeAnswer = true;
+                            break;
+                        case ProviderConstants.NAME_ERROR:
+                            throw new NameNotFoundException("Name " + name +
+                                    " was not found");
+                        case ProviderConstants.SERVER_FAILURE:
+                        case ProviderConstants.FORMAT_ERROR:
+                        case ProviderConstants.NOT_IMPLEMENTED:
+                        case ProviderConstants.REFUSED:        
+                        default:
+                        }
+                    } catch (DomainProtocolException e) {
+                        ProviderMgr.logger.log(Level.WARNING,
+                                "Error while parsing of DNS message", e);
+                    }    
+                } // if received
+                if (completeAnswer) {
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine(
+                                "list: Complete answer received");
+                    }
+                    break authServersLoop;
+                }
+            } // address loop 
+        } // authoritative servers loop
+        
+        if (!completeAnswer) {
+            // found nothing
+            throw new ServiceUnavailableException(
+                    "Unable to perform zone transfer");
+        }
+        // SRV _Proto prefix support - filter all records that don't have given
+        // _Proto field
+        if (proto != null) {
+            Vector answerVect2 = new Vector();
+
+            for (int i = 0; i < answerVect.size(); i++) {
+                ResourceRecord rr = (ResourceRecord)
+                        answerVect.elementAt(i);
+                StringTokenizer st = new StringTokenizer(rr.getName(),
+                        ".");
+                String token = null;
+                boolean valid = false;
+
+                if (st.hasMoreTokens()) {
+                    token = st.nextToken();
+                    if (token.length() > 0 && token.charAt(0) == '_' &&
+                            st.hasMoreTokens())
+                    {
+                        token = st.nextToken();
+                        if (token.equalsIgnoreCase(proto)) {
+                            valid = true;
+                        }
+                    }
+                }
+                if (valid) {
+                    answerVect2.addElement(rr);
+                }
+            }
+            answerVect = answerVect2;
+        }
+        return answerVect.elements();
+    }
+
+    /**
+     * Adds initial DNS server the resolver should start with. Trying underlying
+     * OS services to determine IP from name.
+     * 
+     * @param name server's name
+     * @param ip server's IP address
+     * @param port port on server
+     */
+    public void addInitialServer(String name, String ip, int port,
+            String zoneName)
+    {
+        SList.Server server = new SList.Server(name, ip, port);
+        SList slist = SList.getInstance();
+
+        if (name == null && ip == null) {
+            throw new NullPointerException("Both name and IP are null");
+        }
+        if (zoneName == null) {
+            throw new NullPointerException("zoneName is null");
+        }
+        // if IP is not given and we don't know this server yet
+        // try to determine IP from underlying OS services
+        if (ip == null && !slist.hasServer(name)) {
+            InetAddress addrObj = TransportMgr.getIPByName_OS(name);
+
+            if (addrObj != null) {
+                server.setIP(ProviderMgr.getIpStr(addrObj.getAddress()));
+            }
+        }
+        // add given zone <-> server pair
+        if (!slist.hasServer(zoneName, server)) {
+            slist.updateEntry(zoneName, server, SList.UNKNOWN);
+        }
+    }
+
+    /**
+     * Query available DNS servers for desired information. This method doesn't
+     * look into the local cache. Drops all answers that contains "server fail"
+     * and "not implemented" answer codes and returns the first "good" answer. 
+     * 
+     * @param request a DNS message that contains the request record  
+     * @param workZone a zone that is closest known (grand-) parent of the
+     *  desired name
+     * @param visitedServers the hash list of servers, that should not be
+     *  examined; this method also appends to this list all server that have
+     *  been visited during execution of this method 
+     * @param tcpOnly <code>true</code> if we want to use TCP protocol only;
+     * otherwise UDP will be tried first
+     * @return the message received; <code>null</code> if none found
+     * @throws DomainProtocolException some domain protocol related error
+     *  occured
+     * @throws SecurityException if the resolver doesn't have the permission 
+     *  to use sockets
+     */
+    Message queryServers(Message request, String workZone,
+            Hashtable visitedServers, boolean tcpOnly)
+            throws DomainProtocolException, SecurityException
+    {
+        QuestionRecord qRecord;
+
+        SList slist = SList.getInstance();
+        SList.Server curServer;
+        byte[] outBuf = new byte[MSG_MAX_BYTES];
+        int outBufLen;
+        byte[] inBuf = new byte[MSG_MAX_BYTES];
+        Message mes = new Message();
+        Message receivedMes = null;
+        int idx = 0;
+        int curTimeout = this.initialTimeout;
+        boolean received = false;
+        boolean parsed = false;
+        boolean correctAnswer = false;
+        int rCode = -1;
+
+      
+        // determine a question
+        if (!request.getQuestionRecords().hasMoreElements()) {
+            throw new IllegalArgumentException("no question record");
+        }
+        qRecord = (QuestionRecord) request.getQuestionRecords().nextElement();
+        // preparing a domain protocol message
+        outBufLen = request.writeBytes(outBuf, 0);
+
+        // sending message and trying to receive an answer
+        for (int round = 0; round < this.timeoutRetries; round++) {
+            Set queriedServers = new HashSet();
+
+            // start of round
+            while (true) {
+                String srvStr = null;
+                long timeBeforeSending;
+                int responseTime = 0;
+
+                received = false;
+                parsed = false;
+                rCode = -1;
+                // get next server
+                curServer = slist.getBestGuess(workZone, visitedServers);
+                if (curServer == null || queriedServers.contains(curServer)) {
+                    // end of round
+                    break;
+                }
+                if (curServer.getIP() == null) {
+                    // if we don't know IP lets start background resolving
+                    // thread
+                    startResolvingThread(curServer.getName(),
+                            qRecord.getQClass());
+                    slist.updateEntry(workZone, curServer,
+                            SList.NETWORK_FAILURE);
+                    queriedServers.add(curServer);
+                    continue;
+                }
+                // send the message and receive the answer
+                try {
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Timeout is set to " +
+                                curTimeout);
+                        ProviderMgr.logger.fine("Querying server \"" +
+                                curServer + "\"");
+                    }
+                    //timeBeforeSending = System.currentTimeMillis();
+                    if (tcpOnly) {
+                        TransportMgr.sendReceiveTCP(curServer.getIP(),
+                                curServer.getPort(),
+                                outBuf, outBufLen,
+                                inBuf, inBuf.length,
+                                curTimeout);
+                    } else {
+                        TransportMgr.sendReceiveUDP(curServer.getIP(),
+                                curServer.getPort(),
+                                outBuf, outBufLen,
+                                inBuf, inBuf.length,
+                                curTimeout);
+                    }
+                    //responseTime = (int) (System.currentTimeMillis() -
+                    //        timeBeforeSending);
+                    //ProviderMgr.logger.fine("Answer received in " +
+                    //        responseTime + " milliseconds");
+                    received = true;
+                } catch (SocketTimeoutException e) {
+                    slist.updateEntry(workZone, curServer, SList.TIMEOUT);
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Socket timeout");
+                    }
+                } catch (DomainProtocolException e) {
+                    // problems with receiving the message
+                    // skipping this server
+                    slist.updateEntry(workZone, curServer,
+                            SList.NETWORK_FAILURE);
+                    ProviderMgr.logger.log(Level.WARNING,
+                            "Connection failure", e);
+                }
+                // parse the message
+                if (received) {
+                    try {
+                        String errMsg = "SERVER: " + srvStr + ":" +
+                                curServer.getPort() + " QNAME: " +
+                                qRecord.getQName();
+                        boolean answerSectionIsTruncated = false;
+
+                        receivedMes = new Message();
+                        idx = 0;
+                        idx = Message.parseMessage(inBuf, idx, receivedMes);
+
+                        if (LogConst.DEBUG) {                            
+                            ProviderMgr.logger.finest("Received message:\n" +
+                                    receivedMes.toString());
+                        }
+                        parsed = true;
+                        // handle a truncation
+                        if (receivedMes.isTc() && !tcpOnly) {
+                            // The Message is truncated.
+                            // Let's try to establish a TCP connection 
+                            // and retransmit the message over that connection.
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine("Message is truncated");
+                                ProviderMgr.logger.fine("Trying to establish " +
+                                        "a connection over TCP");
+                            }
+                            try {
+                                Message receivedMesTcp;
+                                int idx2;
+
+                                TransportMgr.sendReceiveTCP(
+                                        curServer.getIP(),
+                                        curServer.getPort(),
+                                        outBuf, outBufLen,
+                                        inBuf, inBuf.length,
+                                        curTimeout);
+                                receivedMesTcp = new Message();
+                                idx2 = Message.parseMessage(inBuf, 0,
+                                        receivedMesTcp);
+                                // complete message was received
+                                if (!receivedMesTcp.isTc()) {
+                                    receivedMes = receivedMesTcp;
+                                    idx = idx2;
+                                }
+                            } catch (Exception e) {
+                                ProviderMgr.logger.log(Level.WARNING,
+                                        "Receiving a complete message" +
+                                        " over TCP failed", e);
+                                if (LogConst.DEBUG) {                            
+                                    ProviderMgr.logger.fine(
+                                            "Parsing the message " +
+                                            "previously received over UDP");
+                                }
+                            }                                
+                        }
+                        // Is the message still truncated?
+                        // (It is possible in case if TCP connection failed)
+                        if (receivedMes.isTc()) {
+                            // check if the ANSWER section is truncated
+                            // or not
+                            if (!receivedMes.getAuthorityRRs()
+                                        .hasMoreElements() &&
+                                !receivedMes.getAdditionalRRs()
+                                        .hasMoreElements())
+                            {
+                                answerSectionIsTruncated = true;
+                            }
+                        }
+                        rCode = receivedMes.getRCode();
+                        if (rCode == ProviderConstants.NO_ERROR)
+                        {
+                            // correct message has been received
+                            slist.updateEntry(workZone, curServer,
+                                    responseTime);
+                            visitedServers.put(curServer, new Object());
+                            if (!answerSectionIsTruncated) {
+                                correctAnswer = true;
+                                break;
+                            }
+                        } else if (rCode == ProviderConstants.SERVER_FAILURE) {
+                            // removing server from list
+                            ProviderMgr.logger.warning("Server failure. " +
+                                    errMsg);
+                            slist.updateEntry(workZone, curServer,
+                                    SList.SERVER_FAILURE);
+                            visitedServers.put(curServer, new Object());
+                        } else if (rCode == ProviderConstants.FORMAT_ERROR) {
+                            // removing server from list
+                            ProviderMgr.logger.warning("Format error. " +
+                                    errMsg);
+                            slist.updateEntry(workZone, curServer,
+                                    SList.SERVER_FAILURE);
+                            visitedServers.put(curServer, new Object());
+                        } else if (rCode == ProviderConstants.NAME_ERROR) {
+                            ProviderMgr.logger.warning("Name error. " +
+                                    errMsg);
+                            if (receivedMes.isAA()) {
+                                slist.updateEntry(workZone, curServer,
+                                        responseTime);
+                                visitedServers.put(curServer, new Object());
+                                correctAnswer = true;
+                                if (LogConst.DEBUG) {                            
+                                    ProviderMgr.logger.fine(
+                                            "Return name error to user");
+                                }
+                                break;
+                            } else {
+                                // This server is not authoritative server for
+                                // this zone. It should not answer with a 
+                                // name error. Probably it is misconfigured.
+                                slist.updateEntry(workZone, curServer,
+                                        SList.SERVER_FAILURE);
+                                visitedServers.put(curServer, new Object());
+                                if (LogConst.DEBUG) {                            
+                                    ProviderMgr.logger.fine(
+                                            "Not authoritative answer. " +
+                                            "Skip it.");
+                                }
+                            }
+                        } else if (rCode == ProviderConstants.NOT_IMPLEMENTED) {
+                            ProviderMgr.logger.warning("Not implemented. " +
+                                    errMsg);
+                            slist.updateEntry(workZone, curServer,
+                                    SList.SERVER_FAILURE);
+                            visitedServers.put(curServer, new Object());
+                        } else if (rCode == ProviderConstants.REFUSED) {
+                            ProviderMgr.logger.warning("Refused. " +
+                                    errMsg);
+                            slist.updateEntry(workZone, curServer,
+                                    SList.SERVER_FAILURE);
+                            visitedServers.put(curServer, new Object());
+                        }
+                    } catch (DomainProtocolException e) {
+                        // removing this server from SLIST
+                        slist.dropServer(workZone, curServer);
+                        ProviderMgr.logger.warning("Unknown error.");
+                    } catch (IndexOutOfBoundsException e) {
+                        // bad message received
+                        slist.dropServer(workZone, curServer);
+                        ProviderMgr.logger.warning("Bad message received: " + 
+                                " IndexOutOfBoundsException.");
+                    }
+                }
+                queriedServers.add(curServer);
+            }
+            // end of round
+
+            if (received & parsed & correctAnswer) 
+            {
+                // correct answer received
+                return receivedMes;
+            }
+            curTimeout *= 2;
+        }
+        // give up - no correct message has been received
+        return null;
+    }
+
+    /**
+     * Analyzes the answer message and constructs an analysis report. 
+     *  
+     * @param request the request has been send to the server
+     * @param answer the answer has been received
+     * @param workZone the current resolver's work zone
+     * @return analysis report
+     * TODO may be optimized 
+     */
+    AnalysisReport analyzeAnswer(Message request, Message answer)
+            throws DomainProtocolException
+    {
+        Enumeration questions = request.getQuestionRecords();
+        Enumeration answerRRs = answer.getAnswerRRs();
+        Enumeration authorityRRs = answer.getAuthorityRRs();
+        Enumeration additionalRRs;
+        QuestionRecord question;
+        Resolver.AnalysisReport report = new AnalysisReport(); 
+
+        // Check the ID.
+        if (request.getId() != answer.getId()) {
+            throw new DomainProtocolException(
+                    "Request and Answer have different ids");
+        }
+        
+        // Determine a question.
+        if (questions.hasMoreElements()) {
+            question = (QuestionRecord) questions.nextElement();
+        } else {
+            throw new IllegalArgumentException("no question record");
+        }
+        // If name error occured - no extra processing needed.
+        if (answer.getRCode() == ProviderConstants.NAME_ERROR) {
+            report.nameError = true;
+            return report;
+        }
+        // check truncation, truncated message should not be cached 
+        if (answer.isTc()) {
+            report.messageWasTruncated = true;
+        }
+        // Analyze answer section.
+        while (answerRRs.hasMoreElements()) {
+            ResourceRecord curRec = (ResourceRecord) answerRRs.nextElement();
+
+            if (question.getQClass() == curRec.getRRClass() ||
+                    question.getQClass() == ProviderConstants.ANY_QCLASS)
+            {
+                if (question.getQType() == ProviderConstants.ANY_QTYPE &&
+                        ProviderMgr.namesAreEqual(curRec.getName(),
+                                question.getQName()))
+                {
+                    // If we query for ANY record types and the server returns
+                    // some record for the SAME domain name we will collect
+                    // all of such records and treat
+                    // this situation as a complete answer for this query.
+                    // We will not perform any more attempts to obtain more 
+                    // records.
+                    
+                    report.records.addElement(curRec);
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Adding " +
+                                ProviderConstants.rrTypeNames[
+                                        curRec.getRRType()]);
+                    }
+                    if (curRec.getRRType() == ProviderConstants.CNAME_TYPE) {
+                        report.aliasInfoWasReceived = true;
+                        report.newName = (String) curRec.getRData();
+                        if (LogConst.DEBUG) {                            
+                            ProviderMgr.logger.fine("Alias \"" +
+                                    report.newName + "\" was received");
+                        }
+                    }
+                    else {
+                        // XXX have we received a complete set of records?
+                        report.completeAnswerWasReceived = true;
+                    }
+                }
+                else if (question.getQType() == curRec.getRRType() &&
+                         ProviderMgr.namesAreEqual(
+                                 question.getQName(), curRec.getName()))
+                {
+                    // This is a situation when we get the record with the
+                    // name and type exactly matching to that we have asked for.
+                    // We will treat this as a complete answer.  
+
+                    report.records.addElement(curRec);
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Adding " +
+                                ProviderConstants.rrTypeNames[
+                                        curRec.getRRType()]);
+                    }
+                    report.completeAnswerWasReceived = true;
+                }
+                else if (curRec.getRRType() == ProviderConstants.CNAME_TYPE &&
+                        ProviderMgr.namesAreEqual(curRec.getName(), 
+                                question.getQName()))
+                {
+                    // This is the case of an alias. If we received an alias for
+                    // the name we have asked the information for then we need
+                    // to change the desired name to this newly received name.
+                    // Then we will try to find necessary information for
+                    // this new name in the current answer. If we fail then
+                    // we will continue our general lookup algorithm with the
+                    // new name instead of an old one. We will query servers
+                    // from the SLIST with this new name.   
+                    
+                    // TODO this is not effective
+                    Enumeration answerRRs2 = answer.getAnswerRRs();
+                    Enumeration additionalRRs2 = answer.getAdditionalRRs();
+                        
+                    report.aliasInfoWasReceived = true;
+                    report.newName = (String) curRec.getRData();
+                    report.extraRecords.addElement(curRec);
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Alias \"" + report.newName +
+                                "\" was received");
+                    }
+                    // if we find the one of desired records in the
+                    // current answer then we will treat the answer as complete
+                    while (answerRRs2.hasMoreElements()) {
+
+                        // Try to look for info about newly received name 
+                        // in ANSWER section.
+
+                        ResourceRecord tmpRec = (ResourceRecord)
+                                answerRRs2.nextElement();
+
+                        if (LogConst.DEBUG) {                            
+                            ProviderMgr.logger.fine(
+                                    "Look for an answer in ANSWER section");
+                        }
+                        if (tmpRec.getRRType() == question.getQType() &&
+                                ProviderMgr.namesAreEqual(
+                                        tmpRec.getName(), report.newName))
+                        {
+                            // the answer is founded in ANSWER section 
+                            report.records.addElement(tmpRec);
+                            if (LogConst.DEBUG) {                                
+                                ProviderMgr.logger.fine("Adding " +
+                                        ProviderConstants.rrTypeNames[
+                                                tmpRec.getRRType()]);
+                            }
+                            report.completeAnswerWasReceived = true;
+                        }
+                    }
+                    while (additionalRRs2.hasMoreElements()) {
+                        // Try to look for info about newly received name 
+                        // in ADDITIONAL section.
+
+                        ResourceRecord tmpRec = (ResourceRecord)
+                                additionalRRs2.nextElement();
+
+                        if (LogConst.DEBUG) {                            
+                            ProviderMgr.logger.fine("Look for an answer in " +
+                                    "ADDITIONAL section");
+                        }
+                        if (tmpRec.getRRType() == question.getQType() &&
+                                ProviderMgr.namesAreEqual(tmpRec.getName(),
+                                        report.newName))
+                        {
+                            // the answer is founded in ADDITIONAL section 
+                            report.records.addElement(tmpRec);
+                            if (LogConst.DEBUG) {                            
+                                ProviderMgr.logger.fine("Adding " +
+                                        ProviderConstants.rrTypeNames[
+                                                tmpRec.getRRType()]);
+                            }
+                            report.completeAnswerWasReceived = true;
+                        }
+                    }
+                    if (report.completeAnswerWasReceived) {
+                        if (LogConst.DEBUG) {                            
+                            ProviderMgr.logger.fine("Complete answer received");
+                        }
+                    }
+                }
+                else {
+                    // We have received some extra records. Let's save it for
+                    // future use.
+                    
+                    // we will treat authoritative answer as a complete answer
+                    // and in no case will perform further actions 
+                    if (answer.isAA()) {
+                        report.completeAnswerWasReceived = true;
+                    }
+                    report.extraRecords.addElement(curRec);
+                    if (LogConst.DEBUG) {                            
+                        ProviderMgr.logger.fine("Adding additional record " +
+                                ProviderConstants.rrTypeNames[
+                                        curRec.getRRType()]);
+                    }
+                }
+            } else {
+                // The record from another DNS class arrived. Just ignore it.
+                if (LogConst.DEBUG) {                            
+                    ProviderMgr.logger.fine("Ignore records from DNS class " +
+                            curRec.getRRClass());
+                }
+            }
+        }
+
+        // analyze authority section
+        // 1. Store all info from authority NS records; try to locate NS IPs
+        //   from additional records in case if it is not present in SLIST;
+        //   start new background lookup process if not found in additional
+        //   section
+        // TODO current implementation isn't effective
+        while (authorityRRs.hasMoreElements()) {
+            ResourceRecord curRec = (ResourceRecord) authorityRRs.nextElement();
+            SList slist = SList.getInstance();
+
+            // save record for future use
+            report.extraRecords.addElement(curRec);
+            // analyze
+            if (curRec.getRRType() == ProviderConstants.NS_TYPE) {
+                String serverName = (String) curRec.getRData();
+                SList.Server server2 = new SList.Server(serverName, null,
+                        ProviderConstants.DEFAULT_DNS_PORT);
+                SList.Server server = slist.getServerByServer(
+                        curRec.getName(), server2);
+
+                report.delegationArrived = true;
+                if (server == null) {
+                    // not found in SLIST
+                    slist.updateEntry(curRec.getName(), server2, SList.UNKNOWN);
+                    report.delegationZones.addElement(curRec.getName());
+                    server = server2;
+                }
+                if (server != null && server.getIP() == null) {
+                    // try to search additional records to obtain server's IP
+                    additionalRRs = answer.getAdditionalRRs();
+                    while (additionalRRs.hasMoreElements()) {
+                        ResourceRecord addRec = (ResourceRecord)
+                                additionalRRs.nextElement();
+
+                        if (ProviderMgr.namesAreEqual(
+                                addRec.getName(), serverName) &&
+                            addRec.getRRType() == ProviderConstants.A_TYPE)
+                        {
+                            server.setIP((String) addRec.getRData());
+                        }
+                    }
+                    if (server.getIP() == null) {
+                        // IP was not found in additional section
+                        // start resolving process in the background
+                        this.startResolvingThread(server.getName(),
+                                curRec.getRRClass());
+                    }
+                }
+                if (LogConst.DEBUG) {                            
+                    ProviderMgr.logger.fine("Delegation \"" + server +
+                            "\" arrived");
+                }
+            } // end of NS type analysis
+        } // end of authority section analysis
+
+        // analyze additional section
+        additionalRRs = answer.getAdditionalRRs();
+        while (additionalRRs.hasMoreElements()) {
+            ResourceRecord addRec = (ResourceRecord)
+                    additionalRRs.nextElement();
+
+            report.extraRecords.addElement(addRec);
+            if (LogConst.DEBUG) {                            
+                ProviderMgr.logger.fine("Adding additional record " +
+                        ProviderConstants.rrTypeNames[addRec.getRRType()]);
+            }
+        }
+
+        // Fixing RRSet TTL issue.
+        // If TTL fields in RRSet are not all the same then we need to set
+        // all TTLs to lowest found value.
+        // See RFC 2181 point 5.2
+        
+        // checking report.records and report.extraRecords
+        for (int k = 0; k < 2; k++) {
+            Vector records = null;
+            HashSet processed = new HashSet();
+
+            switch (k) {
+                case 0:
+                    records = report.records;
+                    break;
+                case 1:
+                    records = report.extraRecords;
+                    break;
+            }
+            for (int i = 0; i < records.size(); i++) {
+                ResourceRecord rr = (ResourceRecord) records.elementAt(i);
+                String key = rr.getName() + " " + rr.getRRClass() + " " +
+                        rr.getRRType();
+                long ttl = rr.getTtl();
+                Vector objToUpdateTTL = new Vector();
+                
+                if (processed.contains(key)) {
+                    continue;
+                }
+                objToUpdateTTL.addElement(rr);
+                // look forward for records with the same NAME CLASS TYPE
+                for (int j = i; j < records.size(); j++) {
+                    ResourceRecord rr2 = (ResourceRecord) records.elementAt(j);
+                    String key2 = rr2.getName() + " " + rr2.getRRClass() + " " +
+                            rr2.getRRType();
+                    long ttl2 = rr2.getTtl();
+    
+                    if (processed.contains(key2)) {
+                        continue;
+                    }
+                    if (key.equals(key2)) {
+                        if (ttl > ttl2) { 
+                            ttl = ttl2;
+                        }
+                        objToUpdateTTL.addElement(rr2);
+                    }
+                }
+                // update TTL if necessary
+                for (int j = 0; j < objToUpdateTTL.size(); j++) {
+                    ResourceRecord rr2 = (ResourceRecord)
+                            objToUpdateTTL.elementAt(j);
+    
+                    if (rr2.getTtl() != ttl) {
+                        rr2.setTtl(ttl);
+                    }
+                }
+                // don't process such NAME CLASS TYPE combination any more
+                processed.add(key);
+            }
+        } // fixing RRSet TTL issue
+
+        return report;
+    }
+
+    /**
+     * Creates a new <code>Message</code> object and fills some of it's
+     * standard fields. 
+     * 
+     * @return created <code>Message</code> object
+     */
+    static Message createMessageForSending(String desiredName, 
+            int recType, int recClass) throws DomainProtocolException
+    {
+        Message mes = new Message();
+        QuestionRecord qr = new QuestionRecord();
+
+        mes.setId(rndGen.nextInt() & 0xffff);
+        mes.setQR(ProviderConstants.QR_QUERY);
+        mes.setOpCode(ProviderConstants.QUERY);
+        mes.setRD(true);
+        mes.setQDCount(1);
+        qr.setQName(desiredName);
+        qr.setQType(recType);
+        qr.setQClass(recClass);
+        mes.addQuestionRecord(qr);
+        return mes;
+    }
+  
+    /**
+     * Starts new resolver thread that will be searching for IP of the
+     * given hostname.
+     * @param hostname hostname to resolve
+     * @param dnsClass DNS class of host
+     */
+    void startResolvingThread(String hostname, int dnsClass) {
+        Thread newThread;
+        Resolver newResolver;
+        Resolver.ThreadListEntry newEntry;
+        int types[] = {ProviderConstants.A_TYPE};
+        int classes[] = new int[1];
+        
+        synchronized (threadListSemaphore) {
+            // check that no currently running thread looks for this hostname 
+            for (int i = 0; i < resolverThreads.size(); i++) {
+                Resolver.ThreadListEntry entry = (Resolver.ThreadListEntry)
+                        resolverThreads.elementAt(i);
+                if (ProviderMgr.namesAreEqual(hostname,
+                                              entry.serverNameToResolve) &&
+                    entry.dnsClass == dnsClass)
+                {
+                    // this hostname is already under investigation
+                    // exiting
+                    return;
+                }
+            }
+            // check if the hostname is already scheduled for resolving
+            for (int i = 0; i < hostnamesToResolve.size(); i++) {
+                Resolver.ThreadListEntry entry = (Resolver.ThreadListEntry)
+                        hostnamesToResolve.elementAt(i);
+                if (ProviderMgr.namesAreEqual(hostname,
+                                              entry.serverNameToResolve) &&
+                    entry.dnsClass == dnsClass)
+                {
+                    // this hostname is already scheduled for resolving
+                    // exiting
+                    return;
+                }
+            }
+            // check that the maximum number of threads is not exceeded
+            if (resolverThreads.size() >= threadNumberLimit) {
+                // maximum possible number of threads is reached already
+                return;
+            }
+            classes[0] = dnsClass;
+            newEntry = new Resolver.ThreadListEntry();
+            newEntry.serverNameToResolve = hostname;
+            newEntry.dnsClass = dnsClass;
+            hostnamesToResolve.addElement(newEntry);
+            // starting new thread that should make further updates by itself
+            newThread = new Thread(this);
+            if (LogConst.DEBUG) {                            
+                ProviderMgr.logger.fine("Starting new resolver thread," +
+                        " target hostname: " + hostname);
+            }
+            newThread.start();
+        }
+    }
+    
+    /**
+     * Start background search of the address of next unresolved server
+     * hostname 
+     */
+    public void run() {
+        SList slist = SList.getInstance();
+        Enumeration foundRecords;
+        Resolver.ThreadListEntry entryToProcess;
+        int [] classes = new int[1];
+
+        // update lists
+        synchronized (threadListSemaphore) {
+            if (hostnamesToResolve.size() > 0) {
+                entryToProcess = (Resolver.ThreadListEntry)
+                        hostnamesToResolve.elementAt(0);
+                hostnamesToResolve.remove(0);
+                entryToProcess.thread = Thread.currentThread();
+                resolverThreads.addElement(entryToProcess);
+            } else {
+                ProviderMgr.logger.warning(
+                        "Resolver thread: no host name to resolve");
+                return;
+            }
+        }
+        // lookup
+        try {
+            classes[0] = entryToProcess.dnsClass;
+            foundRecords = lookup(entryToProcess.serverNameToResolve,
+                    new int[] {ProviderConstants.A_TYPE},
+                    classes);
+            while (foundRecords != null && foundRecords.hasMoreElements()) {
+                // we will take all A records and store all of them in SLIST
+                ResourceRecord rr = (ResourceRecord)
+                        foundRecords.nextElement();
+
+                if (rr.getRRType() == ProviderConstants.A_TYPE) {
+                    slist.setServerIP(entryToProcess.serverNameToResolve,
+                            (String) rr.getRData());
+                }
+            }
+        }
+        catch (NamingException e) {
+            // just ignore it
+        }
+        // update resolver threads list, remove info about current thread
+        synchronized (threadListSemaphore) {
+            for (int i = 0; i < resolverThreads.size(); i++) {
+                Resolver.ThreadListEntry entry = (Resolver.ThreadListEntry)
+                        resolverThreads.elementAt(i);
+
+                if (ProviderMgr.namesAreEqual(
+                        entryToProcess.serverNameToResolve,
+                        entry.serverNameToResolve) &&
+                    entryToProcess.dnsClass == entry.dnsClass)
+                {
+                    resolverThreads.remove(i);
+                    break;
+                }
+            }
+        }
+        // exiting
+    }
+    
+    
+    /**
+     * Analysis report. 
+     * @author Alexei Zakharov
+     * @see Resolver#analyzeAnswer(Message, Message)
+     */
+    static class AnalysisReport {
+
+        boolean completeAnswerWasReceived = false;
+        boolean nameError = false;
+        boolean delegationArrived = false;
+        boolean aliasInfoWasReceived = false;
+        boolean messageWasTruncated = false;
+        Vector records;
+        Vector delegationZones;
+        String newName = null;
+        Vector extraRecords;
+
+        AnalysisReport() {
+            records = new Vector();
+            delegationZones = new Vector();
+            extraRecords = new Vector();
+        }
+
+    }
+}

Propchange: incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/Resolver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/ResolverCache.java
URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/ResolverCache.java?rev=410240&view=auto
==============================================================================
--- incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/ResolverCache.java (added)
+++ incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/ResolverCache.java Tue May 30 04:24:59 2006
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @author Alexei Y. Zakharov
+ * @version $Revision: 1.1.2.4 $
+ */
+
+package org.apache.harmony.jndi.provider.dns;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * A cache for received resource records. Common for all active resolvers.
+ * 
+ * @author Alexei Zakharov
+ * @version $Revision: 1.1.2.4 $
+ * TODO handling of records with TTL set to 0; should not be cached.
+ */
+class ResolverCache {
+
+    /** keys - zone & host names; values - vectors with RRs */
+    Hashtable names = new Hashtable();
+
+    /** 
+     * Since <code>ResolverCache</code> is singleton class its constructor
+     *  should be hidden.
+     */
+    private ResolverCache() {
+        names = new Hashtable();
+    }
+
+    private static ResolverCache instance = null;
+
+    /**
+     * <code>ResolverCache</code> is a singleton class.
+     * @return active instance of <code>ResolverCache</code>
+     */
+    static ResolverCache getInstance() {
+        if (instance == null) {
+            instance = new ResolverCache();
+        }
+        return instance;
+    }
+    
+    /**
+     * Looks through the cache and returns all suitable resource records
+     * @param question a question record that determines which records we want
+     *  to get from the cache
+     * @return Enumeration of found Resource Records.
+     */
+    synchronized Enumeration get(QuestionRecord question)
+    {
+        String name = question.getQName().toLowerCase();
+        Vector vect = (Vector) names.get(name);
+        int qClass = question.getQClass();
+        int qType = question.getQType();
+        Vector resVect = new Vector();
+
+        if (vect != null) {
+            for (int i = 0; i < vect.size(); i++) {
+                CacheEntry curEntry = (CacheEntry) vect.elementAt(i);
+                ResourceRecord curRR = curEntry.getRR();
+
+                if (curEntry.getBestBefore() < System.currentTimeMillis()) {
+                    // the record is out of date
+                    vect.removeElementAt(i--);
+                    continue;
+                }
+                if (qClass == ProviderConstants.ANY_QCLASS ||
+                        qClass != curRR.getRRClass()) {
+                    continue;
+                }
+                if (qType == ProviderConstants.ANY_QTYPE ||
+                        qType != curRR.getRRType()) {
+                    continue;
+                }
+                resVect.addElement(curRR);
+            }
+        }
+        return resVect.elements();
+    }
+
+    /**
+     * Puts element into the cache. Doesn't put records with zero TTLs.
+     * Doesn't put records with bad TTLs.
+     * @param record a resource record to insert
+     */
+    synchronized void put(ResourceRecord record)
+    {
+        String name = record.getName().toLowerCase();
+        Vector vect = (Vector) names.get(name);
+        long curTime = System.currentTimeMillis();
+        CacheEntry entry = null;
+
+        if (vect == null) {
+            vect = new Vector();
+            names.put(name, vect);
+        }
+        // TTL should be between 0 and 2^31; if greater - should be set to 0
+        // See RFC 2181 point 8
+        if (record.getTtl() >> 31 != 0) {
+            record.setTtl(0);
+        }
+        // skip records with wildcards in names or with zero TTL 
+        if (record.getTtl() > 0 && (record.getName().indexOf('*') == -1)) {
+            entry = new CacheEntry(record, curTime + record.getTtl());
+            // remove old occurence if any
+            for (int i = 0; i < vect.size(); i++) {
+                CacheEntry exEntry = (CacheEntry) vect.elementAt(i);
+                ResourceRecord exRec = exEntry.rr;
+
+                if (ProviderMgr.namesAreEqual(record.getName(), exRec.getName())
+                        && record.getRRClass() == exRec.getRRClass()
+                        && record.getRRType() == exRec.getRRType())
+                {
+                    if (record.getRData() != null && exRec.getRData() != null &&
+                            record.getRData().equals(exRec.getRData())) {
+                        vect.remove(i);
+                        break;
+                    }
+                }
+            }
+            vect.addElement(entry);
+        }
+    }
+
+    /**
+     * Removes all cached entries.
+     */
+    synchronized void clear() {
+        names = new Hashtable();
+    }
+
+    // additional class
+    
+    /**
+     * Represents SLIST cache entry.
+     * @author Alexei Zakharov
+     */
+    static class CacheEntry {
+
+        private ResourceRecord rr;
+        private long bestBefore;
+
+        /**
+         * Constructs new cache entry.
+         * @param rr Resource Record
+         * @param bestBefore best before (time in millis)
+         */
+        public CacheEntry(ResourceRecord rr, long bestBefore) {
+            this.rr = rr;
+            this.bestBefore = bestBefore;
+        }
+
+        
+        /**
+         * @return Returns the bestBefore.
+         */
+        public long getBestBefore() {
+            return bestBefore;
+        }
+        /**
+         * @return Returns the Resource Record.
+         */
+        public ResourceRecord getRR() {
+            return rr;
+        }
+    }
+    
+}

Propchange: incubator/harmony/enhanced/classlib/trunk/modules/jndi/src/main/java/org/apache/harmony/jndi/provider/dns/ResolverCache.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message