001: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.util.*;
006: import java.io.*;
007: import java.net.*;
008:
009: /**
010: * An implementation of Resolver that can send queries to multiple servers,
011: * sending the queries multiple times if necessary.
012: * @see Resolver
013: *
014: * @author Brian Wellington
015: */
016:
017: public class ExtendedResolver implements Resolver {
018:
019: private static class Resolution implements ResolverListener {
020: Resolver[] resolvers;
021: int[] sent;
022: Object[] inprogress;
023: int retries;
024: int outstanding;
025: boolean done;
026: Message query;
027: Message response;
028: Throwable thrown;
029: ResolverListener listener;
030:
031: public Resolution(ExtendedResolver eres, Message query) {
032: List l = eres.resolvers;
033: resolvers = (Resolver[]) l.toArray(new Resolver[l.size()]);
034: if (eres.loadBalance) {
035: int nresolvers = resolvers.length;
036: /*
037: * Note: this is not synchronized, since the
038: * worst thing that can happen is a random
039: * ordering, which is ok.
040: */
041: int start = eres.lbStart++ % nresolvers;
042: if (eres.lbStart > nresolvers)
043: eres.lbStart %= nresolvers;
044: if (start > 0) {
045: Resolver[] shuffle = new Resolver[nresolvers];
046: for (int i = 0; i < nresolvers; i++) {
047: int pos = (i + start) % nresolvers;
048: shuffle[i] = resolvers[pos];
049: }
050: resolvers = shuffle;
051: }
052: }
053: sent = new int[resolvers.length];
054: inprogress = new Object[resolvers.length];
055: retries = eres.retries;
056: this .query = query;
057: }
058:
059: /* Asynchronously sends a message. */
060: public void send(int n) {
061: sent[n]++;
062: outstanding++;
063: try {
064: inprogress[n] = resolvers[n].sendAsync(query, this );
065: } catch (Throwable t) {
066: thrown = t;
067: done = true;
068: if (listener == null) {
069: notifyAll();
070: return;
071: }
072: }
073: }
074:
075: /* Start a synchronous resolution */
076: public Message start() throws IOException {
077: try {
078: /*
079: * First, try sending synchronously. If this works,
080: * we're done. Otherwise, we'll get an exception
081: * and continue. It would be easier to call send(0),
082: * but this avoids a thread creation. If and when
083: * SimpleResolver.sendAsync() can be made to not
084: * create a thread, this could be changed.
085: */
086: sent[0]++;
087: outstanding++;
088: inprogress[0] = new Object();
089: return resolvers[0].send(query);
090: } catch (Exception e) {
091: /*
092: * This will either cause more queries to be sent
093: * asynchronously or will set the 'done' flag.
094: */
095: handleException(inprogress[0], e);
096: }
097: if (!done) {
098: /*
099: * Wait for a successful response or for each
100: * subresolver to fail.
101: */
102: synchronized (this ) {
103: while (!done) {
104: try {
105: wait();
106: } catch (InterruptedException e) {
107: }
108: }
109: }
110: }
111: /* Return the response or throw an exception */
112: if (response != null)
113: return response;
114: else if (thrown instanceof IOException)
115: throw (IOException) thrown;
116: else if (thrown instanceof RuntimeException)
117: throw (RuntimeException) thrown;
118: else if (thrown instanceof Error)
119: throw (Error) thrown;
120: else
121: throw new IllegalStateException(
122: "ExtendedResolver failure");
123: }
124:
125: /* Start an asynchronous resolution */
126: public void startAsync(ResolverListener listener) {
127: this .listener = listener;
128: send(0);
129: }
130:
131: /*
132: * Receive a response. If the resolution hasn't been completed,
133: * either wake up the blocking thread or call the callback.
134: */
135: public void receiveMessage(Object id, Message m) {
136: if (Options.check("verbose"))
137: System.err.println("ExtendedResolver: "
138: + "received message");
139: synchronized (this ) {
140: if (done)
141: return;
142: response = m;
143: done = true;
144: if (listener == null) {
145: notifyAll();
146: return;
147: }
148: }
149: listener.receiveMessage(this , response);
150: }
151:
152: /*
153: * Receive an exception. If the resolution has been completed,
154: * do nothing. Otherwise make progress.
155: */
156: public void handleException(Object id, Exception e) {
157: if (Options.check("verbose"))
158: System.err.println("ExtendedResolver: got " + e);
159: synchronized (this ) {
160: outstanding--;
161: if (done)
162: return;
163: int n;
164: for (n = 0; n < inprogress.length; n++)
165: if (inprogress[n] == id)
166: break;
167: /* If we don't know what this is, do nothing. */
168: if (n == inprogress.length)
169: return;
170: boolean startnext = false;
171: boolean waiting = false;
172: /*
173: * If this is the first response from server n,
174: * we should start sending queries to server n + 1.
175: */
176: if (sent[n] == 1 && n < resolvers.length - 1)
177: startnext = true;
178: if (e instanceof InterruptedIOException) {
179: /* Got a timeout; resend */
180: if (sent[n] < retries)
181: send(n);
182: if (thrown == null)
183: thrown = e;
184: } else if (e instanceof SocketException) {
185: /*
186: * Problem with the socket; don't resend
187: * on it
188: */
189: if (thrown == null
190: || thrown instanceof InterruptedIOException)
191: thrown = e;
192: } else {
193: /*
194: * Problem with the response; don't resend
195: * on the same socket.
196: */
197: thrown = e;
198: }
199: if (done)
200: return;
201: if (startnext)
202: send(n + 1);
203: if (done)
204: return;
205: if (outstanding == 0) {
206: /*
207: * If we're done and this is synchronous,
208: * wake up the blocking thread.
209: */
210: done = true;
211: if (listener == null) {
212: notifyAll();
213: return;
214: }
215: }
216: if (!done)
217: return;
218: }
219: /* If we're done and this is asynchronous, call the callback. */
220: if (!(thrown instanceof Exception))
221: thrown = new RuntimeException(thrown.getMessage());
222: listener.handleException(this , (Exception) thrown);
223: }
224: }
225:
226: private static final int quantum = 5;
227:
228: private List resolvers;
229: private boolean loadBalance = false;
230: private int lbStart = 0;
231: private int retries = 3;
232:
233: private void init() {
234: resolvers = new ArrayList();
235: }
236:
237: /**
238: * Creates a new Extended Resolver. The default ResolverConfig is used to
239: * determine the servers for which SimpleResolver contexts should be
240: * initialized.
241: * @see SimpleResolver
242: * @see ResolverConfig
243: * @exception UnknownHostException Failure occured initializing SimpleResolvers
244: */
245: public ExtendedResolver() throws UnknownHostException {
246: init();
247: String[] servers = ResolverConfig.getCurrentConfig().servers();
248: if (servers != null) {
249: for (int i = 0; i < servers.length; i++) {
250: Resolver r = new SimpleResolver(servers[i]);
251: r.setTimeout(quantum);
252: resolvers.add(r);
253: }
254: } else
255: resolvers.add(new SimpleResolver());
256: }
257:
258: /**
259: * Creates a new Extended Resolver
260: * @param servers An array of server names for which SimpleResolver
261: * contexts should be initialized.
262: * @see SimpleResolver
263: * @exception UnknownHostException Failure occured initializing SimpleResolvers
264: */
265: public ExtendedResolver(String[] servers)
266: throws UnknownHostException {
267: init();
268: for (int i = 0; i < servers.length; i++) {
269: Resolver r = new SimpleResolver(servers[i]);
270: r.setTimeout(quantum);
271: resolvers.add(r);
272: }
273: }
274:
275: /**
276: * Creates a new Extended Resolver
277: * @param res An array of pre-initialized Resolvers is provided.
278: * @see SimpleResolver
279: * @exception UnknownHostException Failure occured initializing SimpleResolvers
280: */
281: public ExtendedResolver(Resolver[] res) throws UnknownHostException {
282: init();
283: for (int i = 0; i < res.length; i++)
284: resolvers.add(res[i]);
285: }
286:
287: public void setPort(int port) {
288: for (int i = 0; i < resolvers.size(); i++)
289: ((Resolver) resolvers.get(i)).setPort(port);
290: }
291:
292: public void setTCP(boolean flag) {
293: for (int i = 0; i < resolvers.size(); i++)
294: ((Resolver) resolvers.get(i)).setTCP(flag);
295: }
296:
297: public void setIgnoreTruncation(boolean flag) {
298: for (int i = 0; i < resolvers.size(); i++)
299: ((Resolver) resolvers.get(i)).setIgnoreTruncation(flag);
300: }
301:
302: public void setEDNS(int level) {
303: for (int i = 0; i < resolvers.size(); i++)
304: ((Resolver) resolvers.get(i)).setEDNS(level);
305: }
306:
307: public void setEDNS(int level, int payloadSize, int flags,
308: List options) {
309: for (int i = 0; i < resolvers.size(); i++)
310: ((Resolver) resolvers.get(i)).setEDNS(level, payloadSize,
311: flags, options);
312: }
313:
314: public void setTSIGKey(TSIG key) {
315: for (int i = 0; i < resolvers.size(); i++)
316: ((Resolver) resolvers.get(i)).setTSIGKey(key);
317: }
318:
319: public void setTimeout(int secs, int msecs) {
320: for (int i = 0; i < resolvers.size(); i++)
321: ((Resolver) resolvers.get(i)).setTimeout(secs, msecs);
322: }
323:
324: public void setTimeout(int secs) {
325: setTimeout(secs, 0);
326: }
327:
328: /**
329: * Sends a message and waits for a response. Multiple servers are queried,
330: * and queries are sent multiple times until either a successful response
331: * is received, or it is clear that there is no successful response.
332: * @param query The query to send.
333: * @return The response.
334: * @throws IOException An error occurred while sending or receiving.
335: */
336: public Message send(Message query) throws IOException {
337: Resolution res = new Resolution(this , query);
338: return res.start();
339: }
340:
341: /**
342: * Asynchronously sends a message to multiple servers, potentially multiple
343: * times, registering a listener to receive a callback on success or exception.
344: * Multiple asynchronous lookups can be performed in parallel. Since the
345: * callback may be invoked before the function returns, external
346: * synchronization is necessary.
347: * @param query The query to send
348: * @param listener The object containing the callbacks.
349: * @return An identifier, which is also a parameter in the callback
350: */
351: public Object sendAsync(final Message query,
352: final ResolverListener listener) {
353: Resolution res = new Resolution(this , query);
354: res.startAsync(listener);
355: return res;
356: }
357:
358: /** Returns the nth resolver used by this ExtendedResolver */
359: public Resolver getResolver(int n) {
360: if (n < resolvers.size())
361: return (Resolver) resolvers.get(n);
362: return null;
363: }
364:
365: /** Returns all resolvers used by this ExtendedResolver */
366: public Resolver[] getResolvers() {
367: return (Resolver[]) resolvers.toArray(new Resolver[resolvers
368: .size()]);
369: }
370:
371: /** Adds a new resolver to be used by this ExtendedResolver */
372: public void addResolver(Resolver r) {
373: resolvers.add(r);
374: }
375:
376: /** Deletes a resolver used by this ExtendedResolver */
377: public void deleteResolver(Resolver r) {
378: resolvers.remove(r);
379: }
380:
381: /** Sets whether the servers should be load balanced.
382: * @param flag If true, servers will be tried in round-robin order. If false,
383: * servers will always be queried in the same order.
384: */
385: public void setLoadBalance(boolean flag) {
386: loadBalance = flag;
387: }
388:
389: /** Sets the number of retries sent to each server per query */
390: public void setRetries(int retries) {
391: this.retries = retries;
392: }
393:
394: }
|