001 /*
002 * Copyright 2011-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2011-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.util.Collections;
026 import java.util.Hashtable;
027 import java.util.Map;
028 import java.util.Properties;
029 import javax.naming.Context;
030 import javax.net.SocketFactory;
031
032 import com.unboundid.util.Debug;
033 import com.unboundid.util.NotMutable;
034 import com.unboundid.util.ThreadSafety;
035 import com.unboundid.util.ThreadSafetyLevel;
036
037
038
039 /**
040 * This class provides a server set implementation that can discover information
041 * about available directory servers through DNS SRV records as described in
042 * <A HREF="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782</A>. DNS SRV records
043 * make it possible for clients to use the domain name system to discover
044 * information about the systems that provide a given service, which can help
045 * avoid the need to explicitly configure clients with the addresses of the
046 * appropriate set of directory servers.
047 * <BR><BR>
048 * The standard service name used to reference LDAP directory servers is
049 * "_ldap._tcp". If client systems have DNS configured properly with an
050 * appropriate search domain, then this may be all that is needed to discover
051 * any available directory servers. Alternately, a record name of
052 * "_ldap._tcp.example.com" may be used to request DNS information about LDAP
053 * servers for the example.com domain. However, there is no technical
054 * requirement that "_ldap._tcp" must be used for this purpose, and it may make
055 * sense to use a different name if there is something special about the way
056 * clients should interact with the servers (e.g., "_ldaps._tcp" would be more
057 * appropriate if LDAP clients need to use SSL when communicating with the
058 * server).
059 * <BR><BR>
060 * DNS SRV records contain a number of components, including:
061 * <UL>
062 * <LI>The address of the system providing the service.</LI>
063 * <LI>The port to which connections should be established to access the
064 * service.</LI>
065 * <LI>The priority assigned to the service record. If there are multiple
066 * servers that provide the associated service, then the priority can be
067 * used to specify the order in which they should be contacted. Records
068 * with a lower priority value wil be used before those with a higher
069 * priority value.</LI>
070 * <LI>The weight assigned to the service record. The weight will be used if
071 * there are multiple service records with the same priority, and it
072 * controls how likely each record is to be chosen. A record with a
073 * weight of 2 is twice as likely to be chosen as a record with the same
074 * priority and a weight of 1.</LI>
075 * </UL>
076 * In the event that multiple SRV records exist for the target service, then the
077 * priorities and weights of those records will be used to determine the order
078 * in which the servers will be tried. Records with a lower priority value will
079 * always be tried before those with a higher priority value. For records with
080 * equal priority values and nonzero weights, then the ratio of those weight
081 * values will be used to control how likely one of those records is to be tried
082 * before another. Records with a weight of zero will always be tried after
083 * records with the same priority and nonzero weights.
084 * <BR><BR>
085 * This server set implementation uses JNDI to communicate with DNS servers in
086 * order to obtain the requested SRV records (although it does not use JNDI for
087 * any LDAP communication). In order to specify which DNS server(s) to query, a
088 * JNDI provider URL must be used. In many cases, a URL of "dns:", which
089 * indicates that the client should use the DNS servers configured for use by
090 * the underlying system, should be sufficient. However, if you wish to use a
091 * specific DNS server then you may explicitly specify it in the URL (e.g.,
092 * "dns://1.2.3.4:53" would attempt to communicate with the DNS server listening
093 * on IP address 1.2.3.4 and port 53). If you wish to specify multiple DNS
094 * servers, you may provide multiple URLs separated with spaces and they will be
095 * tried in the order in which they were included in the list until a response
096 * can be retrieved (e.g., for a provider URL of "dns://1.2.3.4 dns://1.2.3.5",
097 * it will first try to use the DNS server running on system with IP address
098 * "1.2.3.4", but if that is not successful then it will try the DNS server
099 * running on the system with IP address "1.2.3.5"). See the <A HREF="
100 * http://download.oracle.com/javase/6/docs/technotes/guides/jndi/jndi-dns.html
101 * "> JNDI DNS service provider documentation</A> for more details on acceptable
102 * formats for the provider URL.
103 */
104 @NotMutable()
105 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
106 public final class DNSSRVRecordServerSet
107 extends ServerSet
108 {
109 /**
110 * The default SRV record name that will be retrieved if none is specified.
111 */
112 private static final String DEFAULT_RECORD_NAME = "_ldap._tcp";
113
114
115
116 /**
117 * The default time-to-live value (1 hour, represented in milliseconds) that
118 * will be used if no alternate value is specified.
119 */
120 private static final long DEFAULT_TTL_MILLIS = 60L * 60L * 1000L;
121
122
123
124 /**
125 * The default provider URL that will be used for specifying which DNS
126 * server(s) to query. The default behavior will be to attempt to determine
127 * which DNS server(s) to use from the underlying system configuration.
128 */
129 private static final String DEFAULT_DNS_PROVIDER_URL = "dns:";
130
131
132
133 // The properties that will be used to initialize the JNDI context.
134 private final Hashtable<String,String> jndiProperties;
135
136 // The connection options to use for newly-created connections.
137 private final LDAPConnectionOptions connectionOptions;
138
139 // The maximum length of time in milliseconds that previously-retrieved
140 // information should be considered valid.
141 private final long ttlMillis;
142
143 // The socket factory that should be used to create connections.
144 private final SocketFactory socketFactory;
145
146 // The cached set of SRV records.
147 private volatile SRVRecordSet recordSet;
148
149 // The name of the DNS SRV record to retrieve.
150 private final String recordName;
151
152 // The DNS provider URL to use.
153 private final String providerURL;
154
155
156
157 /**
158 * Creates a new instance of this server set that will use the specified DNS
159 * record name, a default DNS provider URL that will attempt to determine DNS
160 * servers from the underlying system configuration, a default TTL of one
161 * hour, round-robin ordering for servers with the same priority, and default
162 * socket factory and connection options.
163 *
164 * @param recordName The name of the DNS SRV record to retrieve. If this is
165 * {@code null}, then a default record name of
166 * "_ldap._tcp" will be used.
167 */
168 public DNSSRVRecordServerSet(final String recordName)
169 {
170 this(recordName, null, DEFAULT_TTL_MILLIS, null, null);
171 }
172
173
174
175 /**
176 * Creates a new instance of this server set that will use the provided
177 * settings.
178 *
179 * @param recordName The name of the DNS SRV record to retrieve. If
180 * this is {@code null}, then a default record name
181 * of "_ldap._tcp" will be used.
182 * @param providerURL The JNDI provider URL that may be used to
183 * specify the DNS server(s) to use. If this is
184 * not specified, then a default URL of "dns:" will
185 * be used, which will attempt to determine the
186 * appropriate servers from the underlying system
187 * configuration.
188 * @param ttlMillis Specifies the maximum length of time in
189 * milliseconds that DNS information should be
190 * cached before it needs to be retrieved again. A
191 * value less than or equal to zero will use the
192 * default TTL of one hour.
193 * @param socketFactory The socket factory that will be used when
194 * creating connections. It may be {@code null} if
195 * the JVM-default socket factory should be used.
196 * @param connectionOptions The set of connection options that should be
197 * used for the connections that are created. It
198 * may be {@code null} if the default connection
199 * options should be used.
200 */
201 public DNSSRVRecordServerSet(final String recordName,
202 final String providerURL, final long ttlMillis,
203 final SocketFactory socketFactory,
204 final LDAPConnectionOptions connectionOptions)
205 {
206 this(recordName, providerURL, null, ttlMillis, socketFactory,
207 connectionOptions);
208 }
209
210
211
212 /**
213 * Creates a new instance of this server set that will use the provided
214 * settings.
215 *
216 * @param recordName The name of the DNS SRV record to retrieve. If
217 * this is {@code null}, then a default record name
218 * of "_ldap._tcp" will be used.
219 * @param providerURL The JNDI provider URL that may be used to
220 * specify the DNS server(s) to use. If this is
221 * not specified, then a default URL of "dns:" will
222 * be used, which will attempt to determine the
223 * appropriate servers from the underlying system
224 * configuration.
225 * @param jndiProperties A set of JNDI-related properties that should be
226 * be used when initializing the context for
227 * interacting with the DNS server via JNDI. If
228 * this is {@code null}, then a default set of
229 * properties will be used.
230 * @param ttlMillis Specifies the maximum length of time in
231 * milliseconds that DNS information should be
232 * cached before it needs to be retrieved again. A
233 * value less than or equal to zero will use the
234 * default TTL of one hour.
235 * @param socketFactory The socket factory that will be used when
236 * creating connections. It may be {@code null} if
237 * the JVM-default socket factory should be used.
238 * @param connectionOptions The set of connection options that should be
239 * used for the connections that are created. It
240 * may be {@code null} if the default connection
241 * options should be used.
242 */
243 public DNSSRVRecordServerSet(final String recordName,
244 final String providerURL,
245 final Properties jndiProperties,
246 final long ttlMillis,
247 final SocketFactory socketFactory,
248 final LDAPConnectionOptions connectionOptions)
249 {
250 this.socketFactory = socketFactory;
251 this.connectionOptions = connectionOptions;
252
253 recordSet = null;
254
255 if (recordName == null)
256 {
257 this.recordName = DEFAULT_RECORD_NAME;
258 }
259 else
260 {
261 this.recordName = recordName;
262 }
263
264 if (providerURL == null)
265 {
266 this.providerURL = DEFAULT_DNS_PROVIDER_URL;
267 }
268 else
269 {
270 this.providerURL = providerURL;
271 }
272
273 this.jndiProperties = new Hashtable<String,String>(10);
274 if (jndiProperties != null)
275 {
276 for (final Map.Entry<Object,Object> e : jndiProperties.entrySet())
277 {
278 this.jndiProperties.put(String.valueOf(e.getKey()),
279 String.valueOf(e.getValue()));
280 }
281 }
282
283 if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY))
284 {
285 this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,
286 "com.sun.jndi.dns.DnsContextFactory");
287 }
288
289 if (! this.jndiProperties.containsKey(Context.PROVIDER_URL))
290 {
291 this.jndiProperties.put(Context.PROVIDER_URL, this.providerURL);
292 }
293
294 if (ttlMillis <= 0L)
295 {
296 this.ttlMillis = DEFAULT_TTL_MILLIS;
297 }
298 else
299 {
300 this.ttlMillis = ttlMillis;
301 }
302 }
303
304
305
306 /**
307 * Retrieves the name of the DNS SRV record to retrieve.
308 *
309 * @return The name of the DNS SRV record to retrieve.
310 */
311 public String getRecordName()
312 {
313 return recordName;
314 }
315
316
317
318 /**
319 * Retrieves the JNDI provider URL that specifies the DNS server(s) to use.
320 *
321 * @return The JNDI provider URL that specifies the DNS server(s) to use.
322 */
323 public String getProviderURL()
324 {
325 return providerURL;
326 }
327
328
329
330 /**
331 * Retrieves an unmodifiable map of properties that will be used to initialize
332 * the JNDI context used to interact with DNS. Note that the map returned
333 * will reflect the actual properties that will be used, and may not exactly
334 * match the properties provided when creating this server set.
335 *
336 * @return An unmodifiable map of properties that will be used to initialize
337 * the JNDI context used to interact with DNS.
338 */
339 public Map<String,String> getJNDIProperties()
340 {
341 return Collections.unmodifiableMap(jndiProperties);
342 }
343
344
345
346 /**
347 * Retrieves the maximum length of time in milliseconds that
348 * previously-retrieved DNS information should be cached before it needs to be
349 * refreshed.
350 *
351 * @return The maximum length of time in milliseconds that
352 * previously-retrieved DNS information should be cached before it
353 * needs to be refreshed.
354 */
355 public long getTTLMillis()
356 {
357 return ttlMillis;
358 }
359
360
361
362 /**
363 * Retrieves the socket factory that will be used when creating connections,
364 * if any.
365 *
366 * @return The socket factory that will be used when creating connections, or
367 * {@code null} if the JVM-default socket factory will be used.
368 */
369 public SocketFactory getSocketFactory()
370 {
371 return socketFactory;
372 }
373
374
375
376 /**
377 * Retrieves the set of connection options to use for connections that are
378 * created, if any.
379 *
380 * @return The set of connection options to use for connections that are
381 * created, or {@code null} if a default set of options should be
382 * used.
383 */
384 public LDAPConnectionOptions getConnectionOptions()
385 {
386 return connectionOptions;
387 }
388
389
390
391 /**
392 * {@inheritDoc}
393 */
394 @Override()
395 public LDAPConnection getConnection()
396 throws LDAPException
397 {
398 return getConnection(null);
399 }
400
401
402
403 /**
404 * {@inheritDoc}
405 */
406 @Override()
407 public LDAPConnection getConnection(
408 final LDAPConnectionPoolHealthCheck healthCheck)
409 throws LDAPException
410 {
411 // If there is no cached record set, or if the cached set is expired, then
412 // try to get a new one.
413 if ((recordSet == null) || recordSet.isExpired())
414 {
415 try
416 {
417 recordSet = SRVRecordSet.getRecordSet(recordName, jndiProperties,
418 ttlMillis);
419 }
420 catch (final LDAPException le)
421 {
422 Debug.debugException(le);
423
424 // We couldn't get a new record set. If we have an existing one, then
425 // it's expired but we'll keep using it anyway because it's better than
426 // nothing. But if we don't have an existing set, then we can't
427 // continue.
428 if (recordSet == null)
429 {
430 throw le;
431 }
432 }
433 }
434
435
436 // Iterate through the record set in an order based on priority and weight.
437 // Take the first one that we can connect to and that satisfies the health
438 // check (if any).
439 LDAPException firstException = null;
440 for (final SRVRecord r : recordSet.getOrderedRecords())
441 {
442 final LDAPConnection conn;
443 try
444 {
445 conn = new LDAPConnection(socketFactory, connectionOptions,
446 r.getAddress(), r.getPort());
447 }
448 catch (final LDAPException le)
449 {
450 Debug.debugException(le);
451 if (firstException == null)
452 {
453 firstException = le;
454 }
455
456 continue;
457 }
458
459 if (healthCheck != null)
460 {
461 try
462 {
463 healthCheck.ensureNewConnectionValid(conn);
464 }
465 catch (final LDAPException le)
466 {
467 Debug.debugException(le);
468 if (firstException == null)
469 {
470 firstException = le;
471 }
472
473 continue;
474 }
475 }
476
477 return conn;
478 }
479
480 // If we've gotten here, then we couldn't connect to any of the servers.
481 // Throw the first exception that we encountered.
482 throw firstException;
483 }
484
485
486
487 /**
488 * {@inheritDoc}
489 */
490 @Override()
491 public void toString(final StringBuilder buffer)
492 {
493 buffer.append("DNSSRVRecordServerSet(recordName='");
494 buffer.append(recordName);
495 buffer.append("', providerURL='");
496 buffer.append(providerURL);
497 buffer.append("', ttlMillis=");
498 buffer.append(ttlMillis);
499
500 if (socketFactory != null)
501 {
502 buffer.append(", socketFactoryClass='");
503 buffer.append(socketFactory.getClass().getName());
504 buffer.append('\'');
505 }
506
507 if (connectionOptions != null)
508 {
509 buffer.append(", connectionOptions");
510 connectionOptions.toString(buffer);
511 }
512
513 buffer.append(')');
514 }
515 }