1// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.util.*;
6import java.io.*;
7import java.net.*;
8
9/**
10 * An implementation of Resolver that can send queries to multiple servers,
11 * sending the queries multiple times if necessary.
12 * @see Resolver
13 *
14 * @author Brian Wellington
15 */
16
17public class ExtendedResolver implements Resolver {
18
19private static class Resolution implements ResolverListener {
20	Resolver [] resolvers;
21	int [] sent;
22	Object [] inprogress;
23	int retries;
24	int outstanding;
25	boolean done;
26	Message query;
27	Message response;
28	Throwable thrown;
29	ResolverListener listener;
30
31	public
32	Resolution(ExtendedResolver eres, Message query) {
33		List l = eres.resolvers;
34		resolvers = (Resolver []) l.toArray (new Resolver[l.size()]);
35		if (eres.loadBalance) {
36			int nresolvers = resolvers.length;
37			/*
38			 * Note: this is not synchronized, since the
39			 * worst thing that can happen is a random
40			 * ordering, which is ok.
41			 */
42			int start = eres.lbStart++ % nresolvers;
43			if (eres.lbStart > nresolvers)
44				eres.lbStart %= nresolvers;
45			if (start > 0) {
46				Resolver [] shuffle = new Resolver[nresolvers];
47				for (int i = 0; i < nresolvers; i++) {
48					int pos = (i + start) % nresolvers;
49					shuffle[i] = resolvers[pos];
50				}
51				resolvers = shuffle;
52			}
53		}
54		sent = new int[resolvers.length];
55		inprogress = new Object[resolvers.length];
56		retries = eres.retries;
57		this.query = query;
58	}
59
60	/* Asynchronously sends a message. */
61	public void
62	send(int n) {
63		sent[n]++;
64		outstanding++;
65		try {
66			inprogress[n] = resolvers[n].sendAsync(query, this);
67		}
68		catch (Throwable t) {
69			synchronized (this) {
70				thrown = t;
71				done = true;
72				if (listener == null) {
73					notifyAll();
74					return;
75				}
76			}
77		}
78	}
79
80	/* Start a synchronous resolution */
81	public Message
82	start() throws IOException {
83		try {
84			/*
85			 * First, try sending synchronously.  If this works,
86			 * we're done.  Otherwise, we'll get an exception
87			 * and continue.  It would be easier to call send(0),
88			 * but this avoids a thread creation.  If and when
89			 * SimpleResolver.sendAsync() can be made to not
90			 * create a thread, this could be changed.
91			 */
92			sent[0]++;
93			outstanding++;
94			inprogress[0] = new Object();
95			return resolvers[0].send(query);
96		}
97		catch (Exception e) {
98			/*
99			 * This will either cause more queries to be sent
100			 * asynchronously or will set the 'done' flag.
101			 */
102			handleException(inprogress[0], e);
103		}
104		/*
105		 * Wait for a successful response or for each
106		 * subresolver to fail.
107		 */
108		synchronized (this) {
109			while (!done) {
110				try {
111					wait();
112				}
113				catch (InterruptedException e) {
114				}
115			}
116		}
117		/* Return the response or throw an exception */
118		if (response != null)
119			return response;
120		else if (thrown instanceof IOException)
121			throw (IOException) thrown;
122		else if (thrown instanceof RuntimeException)
123			throw (RuntimeException) thrown;
124		else if (thrown instanceof Error)
125			throw (Error) thrown;
126		else
127			throw new IllegalStateException
128				("ExtendedResolver failure");
129	}
130
131	/* Start an asynchronous resolution */
132	public void
133	startAsync(ResolverListener listener) {
134		this.listener = listener;
135		send(0);
136	}
137
138	/*
139	 * Receive a response.  If the resolution hasn't been completed,
140	 * either wake up the blocking thread or call the callback.
141	 */
142	public void
143	receiveMessage(Object id, Message m) {
144		if (Options.check("verbose"))
145			System.err.println("ExtendedResolver: " +
146					   "received message");
147		synchronized (this) {
148			if (done)
149				return;
150			response = m;
151			done = true;
152			if (listener == null) {
153				notifyAll();
154				return;
155			}
156		}
157		listener.receiveMessage(this, response);
158	}
159
160	/*
161	 * Receive an exception.  If the resolution has been completed,
162	 * do nothing.  Otherwise make progress.
163	 */
164	public void
165	handleException(Object id, Exception e) {
166		if (Options.check("verbose"))
167			System.err.println("ExtendedResolver: got " + e);
168		synchronized (this) {
169			outstanding--;
170			if (done)
171				return;
172			int n;
173			for (n = 0; n < inprogress.length; n++)
174				if (inprogress[n] == id)
175					break;
176			/* If we don't know what this is, do nothing. */
177			if (n == inprogress.length)
178				return;
179			boolean startnext = false;
180			/*
181			 * If this is the first response from server n,
182			 * we should start sending queries to server n + 1.
183			 */
184			if (sent[n] == 1 && n < resolvers.length - 1)
185				startnext = true;
186			if (e instanceof InterruptedIOException) {
187				/* Got a timeout; resend */
188				if (sent[n] < retries)
189					send(n);
190				if (thrown == null)
191					thrown = e;
192			} else if (e instanceof SocketException) {
193				/*
194				 * Problem with the socket; don't resend
195				 * on it
196				 */
197				if (thrown == null ||
198				    thrown instanceof InterruptedIOException)
199					thrown = e;
200			} else {
201				/*
202				 * Problem with the response; don't resend
203				 * on the same socket.
204				 */
205				thrown = e;
206			}
207			if (done)
208				return;
209			if (startnext)
210				send(n + 1);
211			if (done)
212				return;
213			if (outstanding == 0) {
214				/*
215				 * If we're done and this is synchronous,
216				 * wake up the blocking thread.
217				 */
218				done = true;
219				if (listener == null) {
220					notifyAll();
221					return;
222				}
223			}
224			if (!done)
225				return;
226		}
227		/* If we're done and this is asynchronous, call the callback. */
228		if (!(thrown instanceof Exception))
229			thrown = new RuntimeException(thrown.getMessage());
230		listener.handleException(this, (Exception) thrown);
231	}
232}
233
234private static final int quantum = 5;
235
236private List resolvers;
237private boolean loadBalance = false;
238private int lbStart = 0;
239private int retries = 3;
240
241private void
242init() {
243	resolvers = new ArrayList();
244}
245
246/**
247 * Creates a new Extended Resolver.  The default ResolverConfig is used to
248 * determine the servers for which SimpleResolver contexts should be
249 * initialized.
250 * @see SimpleResolver
251 * @see ResolverConfig
252 * @exception UnknownHostException Failure occured initializing SimpleResolvers
253 */
254public
255ExtendedResolver() throws UnknownHostException {
256	init();
257	String [] servers = ResolverConfig.getCurrentConfig().servers();
258	if (servers != null) {
259		for (int i = 0; i < servers.length; i++) {
260			Resolver r = new SimpleResolver(servers[i]);
261			r.setTimeout(quantum);
262			resolvers.add(r);
263		}
264	}
265	else
266		resolvers.add(new SimpleResolver());
267}
268
269/**
270 * Creates a new Extended Resolver
271 * @param servers An array of server names for which SimpleResolver
272 * contexts should be initialized.
273 * @see SimpleResolver
274 * @exception UnknownHostException Failure occured initializing SimpleResolvers
275 */
276public
277ExtendedResolver(String [] servers) throws UnknownHostException {
278	init();
279	for (int i = 0; i < servers.length; i++) {
280		Resolver r = new SimpleResolver(servers[i]);
281		r.setTimeout(quantum);
282		resolvers.add(r);
283	}
284}
285
286/**
287 * Creates a new Extended Resolver
288 * @param res An array of pre-initialized Resolvers is provided.
289 * @see SimpleResolver
290 * @exception UnknownHostException Failure occured initializing SimpleResolvers
291 */
292public
293ExtendedResolver(Resolver [] res) throws UnknownHostException {
294	init();
295	for (int i = 0; i < res.length; i++)
296		resolvers.add(res[i]);
297}
298
299public void
300setPort(int port) {
301	for (int i = 0; i < resolvers.size(); i++)
302		((Resolver)resolvers.get(i)).setPort(port);
303}
304
305public void
306setTCP(boolean flag) {
307	for (int i = 0; i < resolvers.size(); i++)
308		((Resolver)resolvers.get(i)).setTCP(flag);
309}
310
311public void
312setIgnoreTruncation(boolean flag) {
313	for (int i = 0; i < resolvers.size(); i++)
314		((Resolver)resolvers.get(i)).setIgnoreTruncation(flag);
315}
316
317public void
318setEDNS(int level) {
319	for (int i = 0; i < resolvers.size(); i++)
320		((Resolver)resolvers.get(i)).setEDNS(level);
321}
322
323public void
324setEDNS(int level, int payloadSize, int flags, List options) {
325	for (int i = 0; i < resolvers.size(); i++)
326		((Resolver)resolvers.get(i)).setEDNS(level, payloadSize,
327						     flags, options);
328}
329
330public void
331setTSIGKey(TSIG key) {
332	for (int i = 0; i < resolvers.size(); i++)
333		((Resolver)resolvers.get(i)).setTSIGKey(key);
334}
335
336public void
337setTimeout(int secs, int msecs) {
338	for (int i = 0; i < resolvers.size(); i++)
339		((Resolver)resolvers.get(i)).setTimeout(secs, msecs);
340}
341
342public void
343setTimeout(int secs) {
344	setTimeout(secs, 0);
345}
346
347/**
348 * Sends a message and waits for a response.  Multiple servers are queried,
349 * and queries are sent multiple times until either a successful response
350 * is received, or it is clear that there is no successful response.
351 * @param query The query to send.
352 * @return The response.
353 * @throws IOException An error occurred while sending or receiving.
354 */
355public Message
356send(Message query) throws IOException {
357	Resolution res = new Resolution(this, query);
358	return res.start();
359}
360
361/**
362 * Asynchronously sends a message to multiple servers, potentially multiple
363 * times, registering a listener to receive a callback on success or exception.
364 * Multiple asynchronous lookups can be performed in parallel.  Since the
365 * callback may be invoked before the function returns, external
366 * synchronization is necessary.
367 * @param query The query to send
368 * @param listener The object containing the callbacks.
369 * @return An identifier, which is also a parameter in the callback
370 */
371public Object
372sendAsync(final Message query, final ResolverListener listener) {
373	Resolution res = new Resolution(this, query);
374	res.startAsync(listener);
375	return res;
376}
377
378/** Returns the nth resolver used by this ExtendedResolver */
379public Resolver
380getResolver(int n) {
381	if (n < resolvers.size())
382		return (Resolver)resolvers.get(n);
383	return null;
384}
385
386/** Returns all resolvers used by this ExtendedResolver */
387public Resolver []
388getResolvers() {
389	return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]);
390}
391
392/** Adds a new resolver to be used by this ExtendedResolver */
393public void
394addResolver(Resolver r) {
395	resolvers.add(r);
396}
397
398/** Deletes a resolver used by this ExtendedResolver */
399public void
400deleteResolver(Resolver r) {
401	resolvers.remove(r);
402}
403
404/** Sets whether the servers should be load balanced.
405 * @param flag If true, servers will be tried in round-robin order.  If false,
406 * servers will always be queried in the same order.
407 */
408public void
409setLoadBalance(boolean flag) {
410	loadBalance = flag;
411}
412
413/** Sets the number of retries sent to each server per query */
414public void
415setRetries(int retries) {
416	this.retries = retries;
417}
418
419}
420