| // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) |
| |
| package org.xbill.DNS; |
| |
| import java.util.*; |
| import java.io.*; |
| import java.net.*; |
| |
| /** |
| * An implementation of Resolver that can send queries to multiple servers, |
| * sending the queries multiple times if necessary. |
| * @see Resolver |
| * |
| * @author Brian Wellington |
| */ |
| |
| public class ExtendedResolver implements Resolver { |
| |
| private static class Resolution implements ResolverListener { |
| Resolver [] resolvers; |
| int [] sent; |
| Object [] inprogress; |
| int retries; |
| int outstanding; |
| boolean done; |
| Message query; |
| Message response; |
| Throwable thrown; |
| ResolverListener listener; |
| |
| public |
| Resolution(ExtendedResolver eres, Message query) { |
| List l = eres.resolvers; |
| resolvers = (Resolver []) l.toArray (new Resolver[l.size()]); |
| if (eres.loadBalance) { |
| int nresolvers = resolvers.length; |
| /* |
| * Note: this is not synchronized, since the |
| * worst thing that can happen is a random |
| * ordering, which is ok. |
| */ |
| int start = eres.lbStart++ % nresolvers; |
| if (eres.lbStart > nresolvers) |
| eres.lbStart %= nresolvers; |
| if (start > 0) { |
| Resolver [] shuffle = new Resolver[nresolvers]; |
| for (int i = 0; i < nresolvers; i++) { |
| int pos = (i + start) % nresolvers; |
| shuffle[i] = resolvers[pos]; |
| } |
| resolvers = shuffle; |
| } |
| } |
| sent = new int[resolvers.length]; |
| inprogress = new Object[resolvers.length]; |
| retries = eres.retries; |
| this.query = query; |
| } |
| |
| /* Asynchronously sends a message. */ |
| public void |
| send(int n) { |
| sent[n]++; |
| outstanding++; |
| try { |
| inprogress[n] = resolvers[n].sendAsync(query, this); |
| } |
| catch (Throwable t) { |
| synchronized (this) { |
| thrown = t; |
| done = true; |
| if (listener == null) { |
| notifyAll(); |
| return; |
| } |
| } |
| } |
| } |
| |
| /* Start a synchronous resolution */ |
| public Message |
| start() throws IOException { |
| try { |
| /* |
| * First, try sending synchronously. If this works, |
| * we're done. Otherwise, we'll get an exception |
| * and continue. It would be easier to call send(0), |
| * but this avoids a thread creation. If and when |
| * SimpleResolver.sendAsync() can be made to not |
| * create a thread, this could be changed. |
| */ |
| sent[0]++; |
| outstanding++; |
| inprogress[0] = new Object(); |
| return resolvers[0].send(query); |
| } |
| catch (Exception e) { |
| /* |
| * This will either cause more queries to be sent |
| * asynchronously or will set the 'done' flag. |
| */ |
| handleException(inprogress[0], e); |
| } |
| /* |
| * Wait for a successful response or for each |
| * subresolver to fail. |
| */ |
| synchronized (this) { |
| while (!done) { |
| try { |
| wait(); |
| } |
| catch (InterruptedException e) { |
| } |
| } |
| } |
| /* Return the response or throw an exception */ |
| if (response != null) |
| return response; |
| else if (thrown instanceof IOException) |
| throw (IOException) thrown; |
| else if (thrown instanceof RuntimeException) |
| throw (RuntimeException) thrown; |
| else if (thrown instanceof Error) |
| throw (Error) thrown; |
| else |
| throw new IllegalStateException |
| ("ExtendedResolver failure"); |
| } |
| |
| /* Start an asynchronous resolution */ |
| public void |
| startAsync(ResolverListener listener) { |
| this.listener = listener; |
| send(0); |
| } |
| |
| /* |
| * Receive a response. If the resolution hasn't been completed, |
| * either wake up the blocking thread or call the callback. |
| */ |
| public void |
| receiveMessage(Object id, Message m) { |
| if (Options.check("verbose")) |
| System.err.println("ExtendedResolver: " + |
| "received message"); |
| synchronized (this) { |
| if (done) |
| return; |
| response = m; |
| done = true; |
| if (listener == null) { |
| notifyAll(); |
| return; |
| } |
| } |
| listener.receiveMessage(this, response); |
| } |
| |
| /* |
| * Receive an exception. If the resolution has been completed, |
| * do nothing. Otherwise make progress. |
| */ |
| public void |
| handleException(Object id, Exception e) { |
| if (Options.check("verbose")) |
| System.err.println("ExtendedResolver: got " + e); |
| synchronized (this) { |
| outstanding--; |
| if (done) |
| return; |
| int n; |
| for (n = 0; n < inprogress.length; n++) |
| if (inprogress[n] == id) |
| break; |
| /* If we don't know what this is, do nothing. */ |
| if (n == inprogress.length) |
| return; |
| boolean startnext = false; |
| /* |
| * If this is the first response from server n, |
| * we should start sending queries to server n + 1. |
| */ |
| if (sent[n] == 1 && n < resolvers.length - 1) |
| startnext = true; |
| if (e instanceof InterruptedIOException) { |
| /* Got a timeout; resend */ |
| if (sent[n] < retries) |
| send(n); |
| if (thrown == null) |
| thrown = e; |
| } else if (e instanceof SocketException) { |
| /* |
| * Problem with the socket; don't resend |
| * on it |
| */ |
| if (thrown == null || |
| thrown instanceof InterruptedIOException) |
| thrown = e; |
| } else { |
| /* |
| * Problem with the response; don't resend |
| * on the same socket. |
| */ |
| thrown = e; |
| } |
| if (done) |
| return; |
| if (startnext) |
| send(n + 1); |
| if (done) |
| return; |
| if (outstanding == 0) { |
| /* |
| * If we're done and this is synchronous, |
| * wake up the blocking thread. |
| */ |
| done = true; |
| if (listener == null) { |
| notifyAll(); |
| return; |
| } |
| } |
| if (!done) |
| return; |
| } |
| /* If we're done and this is asynchronous, call the callback. */ |
| if (!(thrown instanceof Exception)) |
| thrown = new RuntimeException(thrown.getMessage()); |
| listener.handleException(this, (Exception) thrown); |
| } |
| } |
| |
| private static final int quantum = 5; |
| |
| private List resolvers; |
| private boolean loadBalance = false; |
| private int lbStart = 0; |
| private int retries = 3; |
| |
| private void |
| init() { |
| resolvers = new ArrayList(); |
| } |
| |
| /** |
| * Creates a new Extended Resolver. The default ResolverConfig is used to |
| * determine the servers for which SimpleResolver contexts should be |
| * initialized. |
| * @see SimpleResolver |
| * @see ResolverConfig |
| * @exception UnknownHostException Failure occured initializing SimpleResolvers |
| */ |
| public |
| ExtendedResolver() throws UnknownHostException { |
| init(); |
| String [] servers = ResolverConfig.getCurrentConfig().servers(); |
| if (servers != null) { |
| for (int i = 0; i < servers.length; i++) { |
| Resolver r = new SimpleResolver(servers[i]); |
| r.setTimeout(quantum); |
| resolvers.add(r); |
| } |
| } |
| else |
| resolvers.add(new SimpleResolver()); |
| } |
| |
| /** |
| * Creates a new Extended Resolver |
| * @param servers An array of server names for which SimpleResolver |
| * contexts should be initialized. |
| * @see SimpleResolver |
| * @exception UnknownHostException Failure occured initializing SimpleResolvers |
| */ |
| public |
| ExtendedResolver(String [] servers) throws UnknownHostException { |
| init(); |
| for (int i = 0; i < servers.length; i++) { |
| Resolver r = new SimpleResolver(servers[i]); |
| r.setTimeout(quantum); |
| resolvers.add(r); |
| } |
| } |
| |
| /** |
| * Creates a new Extended Resolver |
| * @param res An array of pre-initialized Resolvers is provided. |
| * @see SimpleResolver |
| * @exception UnknownHostException Failure occured initializing SimpleResolvers |
| */ |
| public |
| ExtendedResolver(Resolver [] res) throws UnknownHostException { |
| init(); |
| for (int i = 0; i < res.length; i++) |
| resolvers.add(res[i]); |
| } |
| |
| public void |
| setPort(int port) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setPort(port); |
| } |
| |
| public void |
| setTCP(boolean flag) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setTCP(flag); |
| } |
| |
| public void |
| setIgnoreTruncation(boolean flag) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setIgnoreTruncation(flag); |
| } |
| |
| public void |
| setEDNS(int level) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setEDNS(level); |
| } |
| |
| public void |
| setEDNS(int level, int payloadSize, int flags, List options) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setEDNS(level, payloadSize, |
| flags, options); |
| } |
| |
| public void |
| setTSIGKey(TSIG key) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setTSIGKey(key); |
| } |
| |
| public void |
| setTimeout(int secs, int msecs) { |
| for (int i = 0; i < resolvers.size(); i++) |
| ((Resolver)resolvers.get(i)).setTimeout(secs, msecs); |
| } |
| |
| public void |
| setTimeout(int secs) { |
| setTimeout(secs, 0); |
| } |
| |
| /** |
| * Sends a message and waits for a response. Multiple servers are queried, |
| * and queries are sent multiple times until either a successful response |
| * is received, or it is clear that there is no successful response. |
| * @param query The query to send. |
| * @return The response. |
| * @throws IOException An error occurred while sending or receiving. |
| */ |
| public Message |
| send(Message query) throws IOException { |
| Resolution res = new Resolution(this, query); |
| return res.start(); |
| } |
| |
| /** |
| * Asynchronously sends a message to multiple servers, potentially multiple |
| * times, registering a listener to receive a callback on success or exception. |
| * Multiple asynchronous lookups can be performed in parallel. Since the |
| * callback may be invoked before the function returns, external |
| * synchronization is necessary. |
| * @param query The query to send |
| * @param listener The object containing the callbacks. |
| * @return An identifier, which is also a parameter in the callback |
| */ |
| public Object |
| sendAsync(final Message query, final ResolverListener listener) { |
| Resolution res = new Resolution(this, query); |
| res.startAsync(listener); |
| return res; |
| } |
| |
| /** Returns the nth resolver used by this ExtendedResolver */ |
| public Resolver |
| getResolver(int n) { |
| if (n < resolvers.size()) |
| return (Resolver)resolvers.get(n); |
| return null; |
| } |
| |
| /** Returns all resolvers used by this ExtendedResolver */ |
| public Resolver [] |
| getResolvers() { |
| return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]); |
| } |
| |
| /** Adds a new resolver to be used by this ExtendedResolver */ |
| public void |
| addResolver(Resolver r) { |
| resolvers.add(r); |
| } |
| |
| /** Deletes a resolver used by this ExtendedResolver */ |
| public void |
| deleteResolver(Resolver r) { |
| resolvers.remove(r); |
| } |
| |
| /** Sets whether the servers should be load balanced. |
| * @param flag If true, servers will be tried in round-robin order. If false, |
| * servers will always be queried in the same order. |
| */ |
| public void |
| setLoadBalance(boolean flag) { |
| loadBalance = flag; |
| } |
| |
| /** Sets the number of retries sent to each server per query */ |
| public void |
| setRetries(int retries) { |
| this.retries = retries; |
| } |
| |
| } |