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 javax.net.SocketFactory;
026
027 import com.unboundid.util.Debug;
028 import com.unboundid.util.NotMutable;
029 import com.unboundid.util.ThreadSafety;
030 import com.unboundid.util.ThreadSafetyLevel;
031
032
033
034 /**
035 * This class provides a server set implementation that can discover information
036 * about available directory servers through DNS SRV records as described in
037 * <A HREF="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782</A>. DNS SRV records
038 * make it possible for clients to use the domain name system to discover
039 * information about the systems that provide a given service, which can help
040 * avoid the need to explicitly configure clients with the addresses of the
041 * appropriate set of directory servers.
042 * <BR><BR>
043 * The standard service name used to reference LDAP directory servers is
044 * "_ldap._tcp". If client systems have DNS configured properly with an
045 * appropriate search domain, then this may be all that is needed to discover
046 * any available directory servers. Alternately, a record name of
047 * "_ldap._tcp.example.com" may be used to request DNS information about LDAP
048 * servers for the example.com domain. However, there is no technical
049 * requirement that "_ldap._tcp" must be used for this purpose, and it may make
050 * sense to use a different name if there is something special about the way
051 * clients should interact with the servers (e.g., "_ldaps._tcp" would be more
052 * appropriate if LDAP clients need to use SSL when communicating with the
053 * server).
054 * <BR><BR>
055 * DNS SRV records contain a number of components, including:
056 * <UL>
057 * <LI>The address of the system providing the service.</LI>
058 * <LI>The port to which connections should be established to access the
059 * service.</LI>
060 * <LI>The priority assigned to the service record. If there are multiple
061 * servers that provide the associated service, then the priority can be
062 * used to specify the order in which they should be contacted. Records
063 * with a lower priority value wil be used before those with a higher
064 * priority value.</LI>
065 * <LI>The weight assigned to the service record. The weight will be used if
066 * there are multiple service records with the same priority, and it
067 * controls how likely each record is to be chosen. A record with a
068 * weight of 2 is twice as likely to be chosen as a record with the same
069 * priority and a weight of 1.</LI>
070 * </UL>
071 * In the event that multiple SRV records exist for the target service, then the
072 * priorities and weights of those records will be used to determine the order
073 * in which the servers will be tried. Records with a lower priority value will
074 * always be tried before those with a higher priority value. For records with
075 * equal priority values and nonzero weights, then the ratio of those weight
076 * values will be used to control how likely one of those records is to be tried
077 * before another. Records with a weight of zero will always be tried after
078 * records with the same priority and nonzero weights.
079 * <BR><BR>
080 * This server set implementation uses JNDI to communicate with DNS servers in
081 * order to obtain the requested SRV records (although it does not use JNDI for
082 * any LDAP communication). In order to specify which DNS server(s) to query, a
083 * JNDI provider URL must be used. In many cases, a URL of "dns:", which
084 * indicates that the client should use the DNS servers configured for use by
085 * the underlying system, should be sufficient. However, if you wish to use a
086 * specific DNS server then you may explicitly specify it in the URL (e.g.,
087 * "dns://1.2.3.4:53" would attempt to communicate with the DNS server listening
088 * on IP address 1.2.3.4 and port 53). If you wish to specify multiple DNS
089 * servers, you may provide multiple URLs separated with spaces and they will be
090 * tried in the order in which they were included in the list until a response
091 * can be retrieved (e.g., for a provider URL of "dns://1.2.3.4 dns://1.2.3.5",
092 * it will first try to use the DNS server running on system with IP address
093 * "1.2.3.4", but if that is not successful then it will try the DNS server
094 * running on the system with IP address "1.2.3.5"). See the <A HREF="
095 * http://download.oracle.com/javase/6/docs/technotes/guides/jndi/jndi-dns.html
096 * "> JNDI DNS service provider documentation</A> for more details on acceptable
097 * formats for the provider URL.
098 */
099 @NotMutable()
100 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
101 public final class DNSSRVRecordServerSet
102 extends ServerSet
103 {
104 /**
105 * The default SRV record name that will be retrieved if none is specified.
106 */
107 private static final String DEFAULT_RECORD_NAME = "_ldap._tcp";
108
109
110
111 /**
112 * The default time-to-live value (1 hour, represented in milliseconds) that
113 * will be used if no alternate value is specified.
114 */
115 private static final long DEFAULT_TTL_MILLIS = 60L * 60L * 1000L;
116
117
118
119 /**
120 * The default provider URL that will be used for specifying which DNS
121 * server(s) to query. The default behavior will be to attempt to determine
122 * which DNS server(s) to use from the underlying system configuration.
123 */
124 private static final String DEFAULT_DNS_PROVIDER_URL = "dns:";
125
126
127
128 // The connection options to use for newly-created connections.
129 private final LDAPConnectionOptions connectionOptions;
130
131 // The maximum length of time in milliseconds that previously-retrieved
132 // information should be considered valid.
133 private final long ttlMillis;
134
135 // The socket factory that should be used to create connections.
136 private final SocketFactory socketFactory;
137
138 // The cached set of SRV records.
139 private volatile SRVRecordSet recordSet;
140
141 // The name of the DNS SRV record to retrieve.
142 private final String recordName;
143
144 // The DNS provider URL to use.
145 private final String providerURL;
146
147
148
149 /**
150 * Creates a new instance of this server set that will use the specified DNS
151 * record name, a default DNS provider URL that will attempt to determine DNS
152 * servers from the underlying system configuration, a default TTL of one
153 * hour, round-robin ordering for servers with the same priority, and default
154 * socket factory and connection options.
155 *
156 * @param recordName The name of the DNS SRV record to retrieve. If this is
157 * {@code null}, then a default record name of
158 * "_ldap._tcp" will be used.
159 */
160 public DNSSRVRecordServerSet(final String recordName)
161 {
162 this(recordName, null, DEFAULT_TTL_MILLIS, null, null);
163 }
164
165
166
167 /**
168 * Creates a new instance of this server set that will use the provided
169 * settings.
170 *
171 * @param recordName The name of the DNS SRV record to retrieve. If
172 * this is {@code null}, then a default record name
173 * of "_ldap._tcp" will be used.
174 * @param providerURL The JNDI provider URL that may be used to
175 * specify the DNS server(s) to use. If this is
176 * not specified, then a default URL of "dns:" will
177 * be used, which will attempt to determine the
178 * appropriate servers from the underlying system
179 * configuration.
180 * @param ttlMillis Specifies the maximum length of time in
181 * milliseconds that DNS information should be
182 * cached before it needs to be retrieved again. A
183 * value less than or equal to zero will use the
184 * default TTL of one hour.
185 * @param socketFactory The socket factory that will be used when
186 * creating connections. It may be {@code null} if
187 * the JVM-default socket factory should be used.
188 * @param connectionOptions The set of connection options that should be
189 * used for the connections that are created. It
190 * may be {@code null} if the default connection
191 * options should be used.
192 */
193 public DNSSRVRecordServerSet(final String recordName,
194 final String providerURL, final long ttlMillis,
195 final SocketFactory socketFactory,
196 final LDAPConnectionOptions connectionOptions)
197 {
198 this.socketFactory = socketFactory;
199 this.connectionOptions = connectionOptions;
200
201 recordSet = null;
202
203 if (recordName == null)
204 {
205 this.recordName = DEFAULT_RECORD_NAME;
206 }
207 else
208 {
209 this.recordName = recordName;
210 }
211
212 if (providerURL == null)
213 {
214 this.providerURL = DEFAULT_DNS_PROVIDER_URL;
215 }
216 else
217 {
218 this.providerURL = providerURL;
219 }
220
221 if (ttlMillis <= 0L)
222 {
223 this.ttlMillis = DEFAULT_TTL_MILLIS;
224 }
225 else
226 {
227 this.ttlMillis = ttlMillis;
228 }
229 }
230
231
232
233 /**
234 * Retrieves the name of the DNS SRV record to retrieve.
235 *
236 * @return The name of the DNS SRV record to retrieve.
237 */
238 public String getRecordName()
239 {
240 return recordName;
241 }
242
243
244
245 /**
246 * Retrieves the JNDI provider URL that specifies the DNS server(s) to use.
247 *
248 * @return The JNDI provider URL that specifies the DNS server(s) to use.
249 */
250 public String getProviderURL()
251 {
252 return providerURL;
253 }
254
255
256
257 /**
258 * Retrieves the maximum length of time in milliseconds that
259 * previously-retrieved DNS information should be cached before it needs to be
260 * refreshed.
261 *
262 * @return The maximum length of time in milliseconds that
263 * previously-retrieved DNS information should be cached before it
264 * needs to be refreshed.
265 */
266 public long getTTLMillis()
267 {
268 return ttlMillis;
269 }
270
271
272
273 /**
274 * Retrieves the socket factory that will be used when creating connections,
275 * if any.
276 *
277 * @return The socket factory that will be used when creating connections, or
278 * {@code null} if the JVM-default socket factory will be used.
279 */
280 public SocketFactory getSocketFactory()
281 {
282 return socketFactory;
283 }
284
285
286
287 /**
288 * Retrieves the set of connection options to use for connections that are
289 * created, if any.
290 *
291 * @return The set of connection options to use for connections that are
292 * created, or {@code null} if a default set of options should be
293 * used.
294 */
295 public LDAPConnectionOptions getConnectionOptions()
296 {
297 return connectionOptions;
298 }
299
300
301
302 /**
303 * {@inheritDoc}
304 */
305 @Override()
306 public LDAPConnection getConnection()
307 throws LDAPException
308 {
309 return getConnection(null);
310 }
311
312
313
314 /**
315 * {@inheritDoc}
316 */
317 @Override()
318 public LDAPConnection getConnection(
319 final LDAPConnectionPoolHealthCheck healthCheck)
320 throws LDAPException
321 {
322 // If there is no cached record set, or if the cached set is expired, then
323 // try to get a new one.
324 if ((recordSet == null) || recordSet.isExpired())
325 {
326 try
327 {
328 recordSet = SRVRecordSet.getRecordSet(recordName, providerURL,
329 ttlMillis);
330 }
331 catch (final LDAPException le)
332 {
333 Debug.debugException(le);
334
335 // We couldn't get a new record set. If we have an existing one, then
336 // it's expired but we'll keep using it anyway because it's better than
337 // nothing. But if we don't have an existing set, then we can't
338 // continue.
339 if (recordSet == null)
340 {
341 throw le;
342 }
343 }
344 }
345
346
347 // Iterate through the record set in an order based on priority and weight.
348 // Take the first one that we can connect to and that satisfies the health
349 // check (if any).
350 LDAPException firstException = null;
351 for (final SRVRecord r : recordSet.getOrderedRecords())
352 {
353 final LDAPConnection conn;
354 try
355 {
356 conn = new LDAPConnection(socketFactory, connectionOptions,
357 r.getAddress(), r.getPort());
358 }
359 catch (final LDAPException le)
360 {
361 Debug.debugException(le);
362 if (firstException == null)
363 {
364 firstException = le;
365 }
366
367 continue;
368 }
369
370 if (healthCheck != null)
371 {
372 try
373 {
374 healthCheck.ensureNewConnectionValid(conn);
375 }
376 catch (final LDAPException le)
377 {
378 Debug.debugException(le);
379 if (firstException == null)
380 {
381 firstException = le;
382 }
383
384 continue;
385 }
386 }
387
388 return conn;
389 }
390
391 // If we've gotten here, then we couldn't connect to any of the servers.
392 // Throw the first exception that we encountered.
393 throw firstException;
394 }
395
396
397
398 /**
399 * {@inheritDoc}
400 */
401 @Override()
402 public void toString(final StringBuilder buffer)
403 {
404 buffer.append("DNSSRVRecordServerSet(recordName='");
405 buffer.append(recordName);
406 buffer.append("', providerURL='");
407 buffer.append(providerURL);
408 buffer.append("', ttlMillis=");
409 buffer.append(ttlMillis);
410
411 if (socketFactory != null)
412 {
413 buffer.append(", socketFactoryClass='");
414 buffer.append(socketFactory.getClass().getName());
415 buffer.append('\'');
416 }
417
418 if (connectionOptions != null)
419 {
420 buffer.append(", connectionOptions");
421 connectionOptions.toString(buffer);
422 }
423
424 buffer.append(')');
425 }
426 }