001    /*
002     * Copyright 2011-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2011-2013 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    }