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