001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2007-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2015 ForgeRock AS.
016 */
017package org.opends.admin.ads;
018
019import static org.opends.admin.ads.util.ConnectionUtils.*;
020import static org.opends.quicksetup.util.Utils.*;
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import javax.naming.NameAlreadyBoundException;
031import javax.naming.NameNotFoundException;
032import javax.naming.NamingEnumeration;
033import javax.naming.NamingException;
034import javax.naming.directory.Attribute;
035import javax.naming.directory.Attributes;
036import javax.naming.directory.BasicAttribute;
037import javax.naming.directory.BasicAttributes;
038import javax.naming.directory.SearchControls;
039import javax.naming.directory.SearchResult;
040import javax.naming.ldap.InitialLdapContext;
041import javax.naming.ldap.LdapName;
042import javax.naming.ldap.Rdn;
043
044import org.forgerock.i18n.LocalizableMessage;
045import org.forgerock.i18n.slf4j.LocalizedLogger;
046import org.opends.admin.ads.util.ConnectionUtils;
047import org.opends.quicksetup.Constants;
048import org.opends.server.config.ConfigConstants;
049import org.opends.server.schema.SchemaConstants;
050
051/** The object of this class represent an OpenDS server. */
052public class ServerDescriptor
053{
054  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
055  private static final String TRUSTSTORE_DN = "cn=ads-truststore";
056
057  private final Map<ADSContext.ServerProperty, Object> adsProperties = new HashMap<>();
058  private final Set<ReplicaDescriptor> replicas = new HashSet<>();
059  private final Map<ServerProperty, Object> serverProperties = new HashMap<>();
060  private TopologyCacheException lastException;
061
062  /**
063   * Enumeration containing the different server properties that we can keep in
064   * the ServerProperty object.
065   */
066  public enum ServerProperty
067  {
068    /** The associated value is a String. */
069    HOST_NAME,
070    /** The associated value is an ArrayList of Integer. */
071    LDAP_PORT,
072    /** The associated value is an ArrayList of Integer. */
073    LDAPS_PORT,
074    /** The associated value is an Integer. */
075    ADMIN_PORT,
076    /** The associated value is an ArrayList of Boolean. */
077    LDAP_ENABLED,
078    /** The associated value is an ArrayList of Boolean. */
079    LDAPS_ENABLED,
080    /** The associated value is an ArrayList of Boolean. */
081    ADMIN_ENABLED,
082    /** The associated value is an ArrayList of Boolean. */
083    STARTTLS_ENABLED,
084    /** The associated value is an ArrayList of Integer. */
085    JMX_PORT,
086    /** The associated value is an ArrayList of Integer. */
087    JMXS_PORT,
088    /** The associated value is an ArrayList of Boolean. */
089    JMX_ENABLED,
090    /** The associated value is an ArrayList of Boolean. */
091    JMXS_ENABLED,
092    /** The associated value is an Integer. */
093    REPLICATION_SERVER_PORT,
094    /** The associated value is a Boolean. */
095    IS_REPLICATION_SERVER,
096    /** The associated value is a Boolean. */
097    IS_REPLICATION_ENABLED,
098    /** The associated value is a Boolean. */
099    IS_REPLICATION_SECURE,
100    /** List of servers specified in the Replication Server configuration. This is a Set of String. */
101    EXTERNAL_REPLICATION_SERVERS,
102    /** The associated value is an Integer. */
103    REPLICATION_SERVER_ID,
104    /**
105     * The instance key-pair public-key certificate. The associated value is a
106     * byte[] (ds-cfg-public-key-certificate;binary).
107     */
108    INSTANCE_PUBLIC_KEY_CERTIFICATE,
109    /** The schema generation ID. */
110    SCHEMA_GENERATION_ID
111  }
112
113  /** Default constructor. */
114  protected ServerDescriptor()
115  {
116  }
117
118  /**
119   * Returns the replicas contained on the server.
120   * @return the replicas contained on the server.
121   */
122  public Set<ReplicaDescriptor> getReplicas()
123  {
124    return new HashSet<>(replicas);
125  }
126
127  /**
128   * Sets the replicas contained on the server.
129   * @param replicas the replicas contained on the server.
130   */
131  public void setReplicas(Set<ReplicaDescriptor> replicas)
132  {
133    this.replicas.clear();
134    this.replicas.addAll(replicas);
135  }
136
137  /**
138   * Returns a Map containing the ADS properties of the server.
139   * @return a Map containing the ADS properties of the server.
140   */
141  public Map<ADSContext.ServerProperty, Object> getAdsProperties()
142  {
143    return adsProperties;
144  }
145
146  /**
147   * Returns a Map containing the properties of the server.
148   * @return a Map containing the properties of the server.
149   */
150  public Map<ServerProperty, Object> getServerProperties()
151  {
152    return serverProperties;
153  }
154
155  /**
156   * Tells whether this server is registered in the ADS or not.
157   * @return <CODE>true</CODE> if the server is registered in the ADS and
158   * <CODE>false</CODE> otherwise.
159   */
160  public boolean isRegistered()
161  {
162    return !adsProperties.isEmpty();
163  }
164
165  /**
166   * Tells whether this server is a replication server or not.
167   * @return <CODE>true</CODE> if the server is a replication server and
168   * <CODE>false</CODE> otherwise.
169   */
170  public boolean isReplicationServer()
171  {
172    return Boolean.TRUE.equals(
173        serverProperties.get(ServerProperty.IS_REPLICATION_SERVER));
174  }
175
176  /**
177   * Tells whether replication is enabled on this server or not.
178   * @return <CODE>true</CODE> if replication is enabled and
179   * <CODE>false</CODE> otherwise.
180   */
181  public boolean isReplicationEnabled()
182  {
183    return Boolean.TRUE.equals(
184        serverProperties.get(ServerProperty.IS_REPLICATION_ENABLED));
185  }
186
187  /**
188   * Returns the String representation of this replication server based
189   * on the information we have ("hostname":"replication port") and
190   * <CODE>null</CODE> if this is not a replication server.
191   * @return the String representation of this replication server based
192   * on the information we have ("hostname":"replication port") and
193   * <CODE>null</CODE> if this is not a replication server.
194   */
195  public String getReplicationServerHostPort()
196  {
197    if (isReplicationServer())
198    {
199      return getReplicationServer(getHostName(), getReplicationServerPort());
200    }
201    return null;
202  }
203
204  /**
205   * Returns the replication server ID of this server and -1 if this is not a
206   * replications server.
207   * @return the replication server ID of this server and -1 if this is not a
208   * replications server.
209   */
210  public int getReplicationServerId()
211  {
212    if (isReplicationServer())
213    {
214      return (Integer) serverProperties.get(ServerProperty.REPLICATION_SERVER_ID);
215    }
216    return -1;
217  }
218
219  /**
220   * Returns the replication port of this server and -1 if this is not a
221   * replications server.
222   * @return the replication port of this server and -1 if this is not a
223   * replications server.
224   */
225  public int getReplicationServerPort()
226  {
227    if (isReplicationServer())
228    {
229      return (Integer) serverProperties.get(
230          ServerProperty.REPLICATION_SERVER_PORT);
231    }
232    return -1;
233  }
234
235  /**
236   * Returns whether the communication with the replication port on the server
237   * is encrypted or not.
238   * @return <CODE>true</CODE> if the communication with the replication port on
239   * the server is encrypted and <CODE>false</CODE> otherwise.
240   */
241  public boolean isReplicationSecure()
242  {
243    return isReplicationServer()
244        && Boolean.TRUE.equals(serverProperties.get(ServerProperty.IS_REPLICATION_SECURE));
245  }
246
247  /**
248   * Sets the ADS properties of the server.
249   * @param adsProperties a Map containing the ADS properties of the server.
250   */
251  public void setAdsProperties(
252      Map<ADSContext.ServerProperty, Object> adsProperties)
253  {
254    this.adsProperties.clear();
255    this.adsProperties.putAll(adsProperties);
256  }
257
258  /**
259   * Returns the host name of the server.
260   * @return the host name of the server.
261   */
262  public String getHostName()
263  {
264    String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
265    if (host != null)
266    {
267      return host;
268    }
269    return (String) adsProperties.get(ADSContext.ServerProperty.HOST_NAME);
270  }
271
272  /**
273   * Returns the URL to access this server using LDAP.  Returns
274   * <CODE>null</CODE> if the server is not configured to listen on an LDAP
275   * port.
276   * @return the URL to access this server using LDAP.
277   */
278  public String getLDAPURL()
279  {
280    return getLDAPUrl0(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, false);
281  }
282
283  /**
284   * Returns the URL to access this server using LDAPS.  Returns
285   * <CODE>null</CODE> if the server is not configured to listen on an LDAPS
286   * port.
287   * @return the URL to access this server using LDAP.
288   */
289  public String getLDAPsURL()
290  {
291    return getLDAPUrl0(ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT, true);
292  }
293
294  private String getLDAPUrl0(ServerProperty enabledProp, ServerProperty portProp, boolean useSSL)
295  {
296    int port = getPort(enabledProp, portProp);
297    if (port != -1)
298    {
299      String host = getHostName();
300      return getLDAPUrl(host, port, useSSL);
301    }
302    return null;
303  }
304
305  private int getPort(ServerProperty enabledProp, ServerProperty portProp)
306  {
307    if (!serverProperties.isEmpty())
308    {
309      return getPort(enabledProp, portProp, -1);
310    }
311    return -1;
312  }
313
314  /**
315   * Returns the URL to access this server using the administration connector.
316   * Returns <CODE>null</CODE> if the server cannot get the administration
317   * connector.
318   * @return the URL to access this server using the administration connector.
319   */
320  public String getAdminConnectorURL()
321  {
322    return getLDAPUrl0(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, true);
323  }
324
325  /**
326   * Returns the list of enabled administration ports.
327   * @return the list of enabled administration ports.
328   */
329  public List<Integer> getEnabledAdministrationPorts()
330  {
331    List<Integer> ports = new ArrayList<>(1);
332    ArrayList<?> s = (ArrayList<?>)serverProperties.get(ServerProperty.ADMIN_ENABLED);
333    ArrayList<?> p = (ArrayList<?>)serverProperties.get(ServerProperty.ADMIN_PORT);
334    if (s != null)
335    {
336      for (int i=0; i<s.size(); i++)
337      {
338        if (Boolean.TRUE.equals(s.get(i)))
339        {
340          ports.add((Integer)p.get(i));
341        }
342      }
343    }
344    return ports;
345  }
346
347  /**
348   * Returns a String of type host-name:port-number for the server.  If
349   * the provided securePreferred is set to true the port that will be used
350   * will be the administration connector port.
351   * @param securePreferred whether to try to use the secure port as part
352   * of the returning String or not.
353   * @return a String of type host-name:port-number for the server.
354   */
355  public String getHostPort(boolean securePreferred)
356  {
357    int port = -1;
358
359    if (!serverProperties.isEmpty())
360    {
361      port = getPort(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, port);
362      if (securePreferred)
363      {
364        port = getPort(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, port);
365      }
366    }
367    else
368    {
369      ArrayList<ADSContext.ServerProperty> enabledAttrs = new ArrayList<>();
370
371      if (securePreferred)
372      {
373        enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED);
374        enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED);
375        enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED);
376      }
377      else
378      {
379        enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED);
380        enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED);
381        enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED);
382      }
383
384      for (ADSContext.ServerProperty prop : enabledAttrs)
385      {
386        Object v = adsProperties.get(prop);
387        if (v != null && "true".equalsIgnoreCase(String.valueOf(v)))
388        {
389          ADSContext.ServerProperty portProp = getPortProperty(prop);
390          Object p = adsProperties.get(portProp);
391          if (p != null)
392          {
393            try
394            {
395              port = Integer.parseInt(String.valueOf(p));
396            }
397            catch (Throwable t)
398            {
399              logger.warn(LocalizableMessage.raw("Error calculating host port: "+t+" in "+
400                  adsProperties, t));
401            }
402            break;
403          }
404          else
405          {
406            logger.warn(LocalizableMessage.raw("Value for "+portProp+" is null in "+
407                adsProperties));
408          }
409        }
410      }
411    }
412
413    String host = getHostName();
414    return host + ":" + port;
415  }
416
417  private ADSContext.ServerProperty getPortProperty(ADSContext.ServerProperty prop)
418  {
419    if (prop == ADSContext.ServerProperty.ADMIN_ENABLED)
420    {
421      return ADSContext.ServerProperty.ADMIN_PORT;
422    }
423    else if (prop == ADSContext.ServerProperty.LDAPS_ENABLED)
424    {
425      return ADSContext.ServerProperty.LDAPS_PORT;
426    }
427    else if (prop == ADSContext.ServerProperty.LDAP_ENABLED)
428    {
429      return ADSContext.ServerProperty.LDAP_PORT;
430    }
431    else
432    {
433      throw new IllegalStateException("Unexpected prop: "+prop);
434    }
435  }
436
437  private int getPort(ServerProperty enabledProp, ServerProperty portProp, int defaultValue)
438  {
439    List<?> s = (List<?>) serverProperties.get(enabledProp);
440    if (s != null)
441    {
442      List<?> p = (List<?>) serverProperties.get(portProp);
443      for (int i=0; i<s.size(); i++)
444      {
445        if (Boolean.TRUE.equals(s.get(i)))
446        {
447          return (Integer) p.get(i);
448        }
449      }
450    }
451    return defaultValue;
452  }
453
454  /**
455   * Returns an Id that is unique for this server.
456   * @return an Id that is unique for this server.
457   */
458  public String getId()
459  {
460    StringBuilder buf = new StringBuilder();
461    if (!serverProperties.isEmpty())
462    {
463      buf.append(serverProperties.get(ServerProperty.HOST_NAME));
464      ServerProperty [] props =
465      {
466          ServerProperty.LDAP_PORT, ServerProperty.LDAPS_PORT,
467          ServerProperty.ADMIN_PORT,
468          ServerProperty.LDAP_ENABLED, ServerProperty.LDAPS_ENABLED,
469          ServerProperty.ADMIN_ENABLED
470      };
471      for (ServerProperty prop : props) {
472        ArrayList<?> s = (ArrayList<?>) serverProperties.get(prop);
473        for (Object o : s) {
474          buf.append(":").append(o);
475        }
476      }
477    }
478    else
479    {
480      ADSContext.ServerProperty[] props =
481      {
482          ADSContext.ServerProperty.HOST_NAME,
483          ADSContext.ServerProperty.LDAP_PORT,
484          ADSContext.ServerProperty.LDAPS_PORT,
485          ADSContext.ServerProperty.ADMIN_PORT,
486          ADSContext.ServerProperty.LDAP_ENABLED,
487          ADSContext.ServerProperty.LDAPS_ENABLED,
488          ADSContext.ServerProperty.ADMIN_ENABLED
489      };
490      for (int i=0; i<props.length; i++)
491      {
492        if (i != 0)
493        {
494          buf.append(":");
495        }
496        buf.append(adsProperties.get(props[i]));
497      }
498    }
499    return buf.toString();
500  }
501
502  /**
503   * Returns the instance-key public-key certificate retrieved from the
504   * truststore backend of the instance referenced through this descriptor.
505   *
506   * @return The public-key certificate of the instance.
507   */
508  public byte[] getInstancePublicKeyCertificate()
509  {
510    return (byte[]) serverProperties.get(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE);
511  }
512
513  /**
514   * Returns the schema generation ID of the server.
515   * @return the schema generation ID of the server.
516   */
517  public String getSchemaReplicationID()
518  {
519    return (String)serverProperties.get(ServerProperty.SCHEMA_GENERATION_ID);
520  }
521
522  /**
523   * Returns the last exception that was encountered reading the configuration
524   * of the server.  Returns null if there was no problem loading the
525   * configuration of the server.
526   * @return the last exception that was encountered reading the configuration
527   * of the server.  Returns null if there was no problem loading the
528   * configuration of the server.
529   */
530  public TopologyCacheException getLastException()
531  {
532    return lastException;
533  }
534
535  /**
536   * Sets the last exception that occurred while reading the configuration of
537   * the server.
538   * @param lastException the last exception that occurred while reading the
539   * configuration of the server.
540   */
541  public void setLastException(TopologyCacheException lastException)
542  {
543    this.lastException = lastException;
544  }
545
546  /**
547   * This methods updates the ADS properties (the ones that were read from
548   * the ADS) with the contents of the server properties (the ones that were
549   * read directly from the server).
550   */
551  public void updateAdsPropertiesWithServerProperties()
552  {
553    adsProperties.put(ADSContext.ServerProperty.HOST_NAME, getHostName());
554    ServerProperty[][] sProps =
555    {
556        {ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT},
557        {ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT},
558        {ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT},
559        {ServerProperty.JMX_ENABLED, ServerProperty.JMX_PORT},
560        {ServerProperty.JMXS_ENABLED, ServerProperty.JMXS_PORT}
561    };
562    ADSContext.ServerProperty[][] adsProps =
563    {
564        {ADSContext.ServerProperty.LDAP_ENABLED,
565          ADSContext.ServerProperty.LDAP_PORT},
566        {ADSContext.ServerProperty.LDAPS_ENABLED,
567          ADSContext.ServerProperty.LDAPS_PORT},
568        {ADSContext.ServerProperty.ADMIN_ENABLED,
569          ADSContext.ServerProperty.ADMIN_PORT},
570        {ADSContext.ServerProperty.JMX_ENABLED,
571          ADSContext.ServerProperty.JMX_PORT},
572        {ADSContext.ServerProperty.JMXS_ENABLED,
573          ADSContext.ServerProperty.JMXS_PORT}
574    };
575
576    for (int i=0; i<sProps.length; i++)
577    {
578      ArrayList<?> s = (ArrayList<?>)serverProperties.get(sProps[i][0]);
579      ArrayList<?> p = (ArrayList<?>)serverProperties.get(sProps[i][1]);
580      if (s != null)
581      {
582        int port = getPort(s, p);
583        if (port == -1)
584        {
585          adsProperties.put(adsProps[i][0], "false");
586          if (!p.isEmpty())
587          {
588            port = (Integer)p.iterator().next();
589          }
590        }
591        else
592        {
593          adsProperties.put(adsProps[i][0], "true");
594        }
595        adsProperties.put(adsProps[i][1], String.valueOf(port));
596      }
597    }
598
599    ArrayList<?> array = (ArrayList<?>)serverProperties.get(
600        ServerProperty.STARTTLS_ENABLED);
601    boolean startTLSEnabled = false;
602    if (array != null && !array.isEmpty())
603    {
604      startTLSEnabled = Boolean.TRUE.equals(array.get(array.size() -1));
605    }
606    adsProperties.put(ADSContext.ServerProperty.STARTTLS_ENABLED, Boolean.toString(startTLSEnabled));
607    adsProperties.put(ADSContext.ServerProperty.ID, getHostPort(true));
608    adsProperties.put(ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE,
609                      getInstancePublicKeyCertificate());
610  }
611
612  private int getPort(List<?> enabled, List<?> port)
613  {
614    for (int j = 0; j < enabled.size(); j++)
615    {
616      if (Boolean.TRUE.equals(enabled.get(j)))
617      {
618        return (Integer) port.get(j);
619      }
620    }
621    return -1;
622  }
623
624  /**
625   * Creates a ServerDescriptor object based on some ADS properties provided.
626   * @param adsProperties the ADS properties of the server.
627   * @return a ServerDescriptor object that corresponds to the provided ADS
628   * properties.
629   */
630  public static ServerDescriptor createStandalone(
631      Map<ADSContext.ServerProperty, Object> adsProperties)
632  {
633    ServerDescriptor desc = new ServerDescriptor();
634    desc.setAdsProperties(adsProperties);
635    return desc;
636  }
637
638  /**
639   * Creates a ServerDescriptor object based on the configuration that we read
640   * using the provided InitialLdapContext.
641   * @param ctx the InitialLdapContext that will be used to read the
642   * configuration of the server.
643   * @param filter the topology cache filter describing the information that
644   * must be retrieved.
645   * @return a ServerDescriptor object that corresponds to the read
646   * configuration.
647   * @throws NamingException if a problem occurred reading the server
648   * configuration.
649   */
650  public static ServerDescriptor createStandalone(InitialLdapContext ctx,
651      TopologyCacheFilter filter)
652  throws NamingException
653  {
654    ServerDescriptor desc = new ServerDescriptor();
655
656    updateLdapConfiguration(desc, ctx);
657    updateAdminConnectorConfiguration(desc, ctx);
658    updateJmxConfiguration(desc, ctx);
659    updateReplicas(desc, ctx, filter);
660    updateReplication(desc, ctx, filter);
661    updatePublicKeyCertificate(desc, ctx);
662    updateMiscellaneous(desc, ctx);
663
664    desc.serverProperties.put(ServerProperty.HOST_NAME,
665        ConnectionUtils.getHostName(ctx));
666
667    return desc;
668  }
669
670  private static void updateLdapConfiguration(ServerDescriptor desc, InitialLdapContext ctx)
671      throws NamingException
672  {
673    SearchControls ctls = new SearchControls();
674    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
675    ctls.setReturningAttributes(
676        new String[] {
677            "ds-cfg-enabled",
678            "ds-cfg-listen-address",
679            "ds-cfg-listen-port",
680            "ds-cfg-use-ssl",
681            "ds-cfg-allow-start-tls",
682            "objectclass"
683        });
684    String filter = "(objectclass=ds-cfg-ldap-connection-handler)";
685
686    LdapName jndiName = new LdapName("cn=config");
687    NamingEnumeration<SearchResult> listeners =
688      ctx.search(jndiName, filter, ctls);
689
690    try
691    {
692      ArrayList<Integer> ldapPorts = new ArrayList<>();
693      ArrayList<Integer> ldapsPorts = new ArrayList<>();
694      ArrayList<Boolean> ldapEnabled = new ArrayList<>();
695      ArrayList<Boolean> ldapsEnabled = new ArrayList<>();
696      ArrayList<Boolean> startTLSEnabled = new ArrayList<>();
697
698      desc.serverProperties.put(ServerProperty.LDAP_PORT, ldapPorts);
699      desc.serverProperties.put(ServerProperty.LDAPS_PORT, ldapsPorts);
700      desc.serverProperties.put(ServerProperty.LDAP_ENABLED, ldapEnabled);
701      desc.serverProperties.put(ServerProperty.LDAPS_ENABLED, ldapsEnabled);
702      desc.serverProperties.put(ServerProperty.STARTTLS_ENABLED,
703          startTLSEnabled);
704
705      while(listeners.hasMore())
706      {
707        SearchResult sr = listeners.next();
708
709        String port = getFirstValue(sr, "ds-cfg-listen-port");
710
711        boolean isSecure = "true".equalsIgnoreCase(
712            getFirstValue(sr, "ds-cfg-use-ssl"));
713
714        boolean enabled = "true".equalsIgnoreCase(
715            getFirstValue(sr, "ds-cfg-enabled"));
716        final Integer portNumber = Integer.valueOf(port);
717        if (isSecure)
718        {
719          ldapsPorts.add(portNumber);
720          ldapsEnabled.add(enabled);
721        }
722        else
723        {
724          ldapPorts.add(portNumber);
725          ldapEnabled.add(enabled);
726          enabled = "true".equalsIgnoreCase(
727              getFirstValue(sr, "ds-cfg-allow-start-tls"));
728          startTLSEnabled.add(enabled);
729        }
730      }
731    }
732    finally
733    {
734      listeners.close();
735    }
736  }
737
738  private static void updateAdminConnectorConfiguration(ServerDescriptor desc, InitialLdapContext ctx)
739      throws NamingException
740  {
741    SearchControls ctls = new SearchControls();
742    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
743    ctls.setReturningAttributes(
744        new String[] {
745            "ds-cfg-listen-port",
746            "objectclass"
747        });
748    String filter = "(objectclass=ds-cfg-administration-connector)";
749
750    LdapName jndiName = new LdapName("cn=config");
751    NamingEnumeration<SearchResult> listeners =
752      ctx.search(jndiName, filter, ctls);
753
754    try
755    {
756      Integer adminConnectorPort = null;
757
758      // we should have a single administration connector
759      while (listeners.hasMore()) {
760        SearchResult sr = listeners.next();
761        String port = getFirstValue(sr, "ds-cfg-listen-port");
762        adminConnectorPort = Integer.valueOf(port);
763      }
764
765      // Even if we have a single port, use an array to be consistent with
766      // other protocols.
767      ArrayList<Integer> adminPorts = new ArrayList<>();
768      ArrayList<Boolean> adminEnabled = new ArrayList<>();
769      if (adminConnectorPort != null)
770      {
771        adminPorts.add(adminConnectorPort);
772        adminEnabled.add(Boolean.TRUE);
773      }
774      desc.serverProperties.put(ServerProperty.ADMIN_PORT, adminPorts);
775      desc.serverProperties.put(ServerProperty.ADMIN_ENABLED, adminEnabled);
776    }
777    finally
778    {
779      listeners.close();
780    }
781  }
782
783  private static void updateJmxConfiguration(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException
784  {
785    SearchControls ctls = new SearchControls();
786    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
787    ctls.setReturningAttributes(
788        new String[] {
789            "ds-cfg-enabled",
790            "ds-cfg-listen-address",
791            "ds-cfg-listen-port",
792            "ds-cfg-use-ssl",
793            "objectclass"
794        });
795    String filter = "(objectclass=ds-cfg-jmx-connection-handler)";
796
797    LdapName jndiName = new LdapName("cn=config");
798    NamingEnumeration<SearchResult> listeners =
799      ctx.search(jndiName, filter, ctls);
800
801    ArrayList<Integer> jmxPorts = new ArrayList<>();
802    ArrayList<Integer> jmxsPorts = new ArrayList<>();
803    ArrayList<Boolean> jmxEnabled = new ArrayList<>();
804    ArrayList<Boolean> jmxsEnabled = new ArrayList<>();
805
806    desc.serverProperties.put(ServerProperty.JMX_PORT, jmxPorts);
807    desc.serverProperties.put(ServerProperty.JMXS_PORT, jmxsPorts);
808    desc.serverProperties.put(ServerProperty.JMX_ENABLED, jmxEnabled);
809    desc.serverProperties.put(ServerProperty.JMXS_ENABLED, jmxsEnabled);
810
811    try
812    {
813      while(listeners.hasMore())
814      {
815        SearchResult sr = listeners.next();
816
817        String port = getFirstValue(sr, "ds-cfg-listen-port");
818
819        boolean isSecure = "true".equalsIgnoreCase(
820            getFirstValue(sr, "ds-cfg-use-ssl"));
821
822        boolean enabled = "true".equalsIgnoreCase(
823            getFirstValue(sr, "ds-cfg-enabled"));
824        Integer portNumber = Integer.valueOf(port);
825        if (isSecure)
826        {
827          jmxsPorts.add(portNumber);
828          jmxsEnabled.add(enabled);
829        }
830        else
831        {
832          jmxPorts.add(portNumber);
833          jmxEnabled.add(enabled);
834        }
835      }
836    }
837    finally
838    {
839      listeners.close();
840    }
841  }
842
843  private static void updateReplicas(ServerDescriptor desc,
844      InitialLdapContext ctx, TopologyCacheFilter cacheFilter)
845  throws NamingException
846  {
847    if (!cacheFilter.searchBaseDNInformation())
848    {
849      return;
850    }
851    SearchControls ctls = new SearchControls();
852    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
853    ctls.setReturningAttributes(
854        new String[] {
855            "ds-cfg-base-dn",
856            "ds-cfg-backend-id",
857            ConfigConstants.ATTR_OBJECTCLASS
858        });
859    String filter = "(objectclass=ds-cfg-backend)";
860
861    LdapName jndiName = new LdapName("cn=config");
862    NamingEnumeration<SearchResult> databases =
863      ctx.search(jndiName, filter, ctls);
864
865    try
866    {
867      while(databases.hasMore())
868      {
869        SearchResult sr = databases.next();
870
871        String id = getFirstValue(sr, "ds-cfg-backend-id");
872
873        if (!isConfigBackend(id) || isSchemaBackend(id))
874        {
875          Set<String> baseDns = getValues(sr, "ds-cfg-base-dn");
876
877          Set<String> entries;
878          if (cacheFilter.searchMonitoringInformation())
879          {
880            entries = getBaseDNEntryCount(ctx, id);
881          }
882          else
883          {
884            entries = new HashSet<>();
885          }
886
887          Set<ReplicaDescriptor> replicas = desc.getReplicas();
888          for (String baseDn : baseDns)
889          {
890            if (isAddReplica(cacheFilter, baseDn))
891            {
892              SuffixDescriptor suffix = new SuffixDescriptor();
893              suffix.setDN(baseDn);
894              ReplicaDescriptor replica = new ReplicaDescriptor();
895              replica.setServer(desc);
896              replica.setObjectClasses(getValues(sr, ConfigConstants.ATTR_OBJECTCLASS));
897              replica.setBackendName(id);
898              replicas.add(replica);
899              HashSet<ReplicaDescriptor> r = new HashSet<>();
900              r.add(replica);
901              suffix.setReplicas(r);
902              replica.setSuffix(suffix);
903              int nEntries = -1;
904              for (String s : entries)
905              {
906                int index = s.indexOf(" ");
907                if (index != -1)
908                {
909                  String dn = s.substring(index + 1);
910                  if (areDnsEqual(baseDn, dn))
911                  {
912                    try
913                    {
914                      nEntries = Integer.parseInt(s.substring(0, index));
915                    }
916                    catch (Throwable t)
917                    {
918                      /* Ignore */
919                    }
920                    break;
921                  }
922                }
923              }
924              replica.setEntries(nEntries);
925            }
926          }
927          desc.setReplicas(replicas);
928        }
929      }
930    }
931    finally
932    {
933      databases.close();
934    }
935  }
936
937  private static boolean isAddReplica(TopologyCacheFilter cacheFilter, String baseDn)
938  {
939    if (cacheFilter.searchAllBaseDNs())
940    {
941      return true;
942    }
943
944    for (String dn : cacheFilter.getBaseDNsToSearch())
945    {
946      if (areDnsEqual(dn, baseDn))
947      {
948        return true;
949      }
950    }
951    return false;
952  }
953
954  private static void updateReplication(ServerDescriptor desc,
955      InitialLdapContext ctx, TopologyCacheFilter cacheFilter)
956  throws NamingException
957  {
958    boolean replicationEnabled = false;
959    SearchControls ctls = new SearchControls();
960    ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
961    ctls.setReturningAttributes(
962        new String[] {
963            "ds-cfg-enabled"
964        });
965    String filter = "(objectclass=ds-cfg-synchronization-provider)";
966
967    LdapName jndiName = new LdapName(
968      "cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config");
969    NamingEnumeration<SearchResult> syncProviders = null;
970
971    try
972    {
973      syncProviders = ctx.search(jndiName, filter, ctls);
974
975      while(syncProviders.hasMore())
976      {
977        SearchResult sr = syncProviders.next();
978
979        if ("true".equalsIgnoreCase(getFirstValue(sr,
980          "ds-cfg-enabled")))
981        {
982          replicationEnabled = true;
983        }
984      }
985    }
986    catch (NameNotFoundException nse)
987    {
988      /* ignore */
989    }
990    finally
991    {
992      if (syncProviders != null)
993      {
994        syncProviders.close();
995      }
996    }
997    desc.serverProperties.put(ServerProperty.IS_REPLICATION_ENABLED,
998        Boolean.valueOf(replicationEnabled));
999
1000    Set<String> allReplicationServers = new LinkedHashSet<>();
1001
1002    if (cacheFilter.searchBaseDNInformation())
1003    {
1004      ctls = new SearchControls();
1005      ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1006      ctls.setReturningAttributes(
1007          new String[] {
1008              "ds-cfg-base-dn",
1009              "ds-cfg-replication-server",
1010              "ds-cfg-server-id"
1011          });
1012      filter = "(objectclass=ds-cfg-replication-domain)";
1013
1014      jndiName = new LdapName(
1015      "cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config");
1016
1017      syncProviders = null;
1018      try
1019      {
1020        syncProviders = ctx.search(jndiName, filter, ctls);
1021
1022        while(syncProviders.hasMore())
1023        {
1024          SearchResult sr = syncProviders.next();
1025
1026          int id = Integer.parseInt(
1027              getFirstValue(sr, "ds-cfg-server-id"));
1028          Set<String> replicationServers = getValues(sr,
1029          "ds-cfg-replication-server");
1030          Set<String> dns = getValues(sr, "ds-cfg-base-dn");
1031          for (String dn : dns)
1032          {
1033            for (ReplicaDescriptor replica : desc.getReplicas())
1034            {
1035              if (areDnsEqual(replica.getSuffix().getDN(), dn))
1036              {
1037                replica.setReplicationId(id);
1038                // Keep the values of the replication servers in lower case
1039                // to make use of Sets as String simpler.
1040                LinkedHashSet<String> repServers = new LinkedHashSet<>();
1041                for (String s: replicationServers)
1042                {
1043                  repServers.add(s.toLowerCase());
1044                }
1045                replica.setReplicationServers(repServers);
1046                allReplicationServers.addAll(repServers);
1047              }
1048            }
1049          }
1050        }
1051      }
1052      catch (NameNotFoundException nse)
1053      {
1054        /* ignore */
1055      }
1056      finally
1057      {
1058        if (syncProviders != null)
1059        {
1060          syncProviders.close();
1061        }
1062      }
1063    }
1064
1065    ctls = new SearchControls();
1066    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1067    ctls.setReturningAttributes(
1068    new String[] {
1069      "ds-cfg-replication-port", "ds-cfg-replication-server",
1070      "ds-cfg-replication-server-id"
1071    });
1072    filter = "(objectclass=ds-cfg-replication-server)";
1073
1074    jndiName = new LdapName("cn=Multimaster "+
1075        "Synchronization,cn=Synchronization Providers,cn=config");
1076
1077    desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER,
1078        Boolean.FALSE);
1079    NamingEnumeration<SearchResult> entries = null;
1080    try
1081    {
1082      entries = ctx.search(jndiName, filter, ctls);
1083
1084      while (entries.hasMore())
1085      {
1086        SearchResult sr = entries.next();
1087
1088        desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER,
1089            Boolean.TRUE);
1090        String v = getFirstValue(sr, "ds-cfg-replication-port");
1091        desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_PORT,
1092            Integer.parseInt(v));
1093        v = getFirstValue(sr, "ds-cfg-replication-server-id");
1094        desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_ID,
1095            Integer.parseInt(v));
1096        Set<String> values = getValues(sr, "ds-cfg-replication-server");
1097        // Keep the values of the replication servers in lower case
1098        // to make use of Sets as String simpler.
1099        LinkedHashSet<String> repServers = new LinkedHashSet<>();
1100        for (String s: values)
1101        {
1102          repServers.add(s.toLowerCase());
1103        }
1104        allReplicationServers.addAll(repServers);
1105        desc.serverProperties.put(ServerProperty.EXTERNAL_REPLICATION_SERVERS,
1106            allReplicationServers);
1107      }
1108    }
1109    catch (NameNotFoundException nse)
1110    {
1111      /* ignore */
1112    }
1113    finally
1114    {
1115      if (entries != null)
1116      {
1117        entries.close();
1118      }
1119    }
1120
1121    boolean replicationSecure = false;
1122    if (replicationEnabled)
1123    {
1124      ctls = new SearchControls();
1125      ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
1126      ctls.setReturningAttributes(
1127      new String[] {"ds-cfg-ssl-encryption"});
1128      filter = "(objectclass=ds-cfg-crypto-manager)";
1129
1130      jndiName = new LdapName("cn=Crypto Manager,cn=config");
1131
1132      entries = ctx.search(jndiName, filter, ctls);
1133
1134      try
1135      {
1136        while (entries.hasMore())
1137        {
1138          SearchResult sr = entries.next();
1139
1140          String v = getFirstValue(sr, "ds-cfg-ssl-encryption");
1141          replicationSecure = "true".equalsIgnoreCase(v);
1142        }
1143      }
1144      finally
1145      {
1146        entries.close();
1147      }
1148    }
1149    desc.serverProperties.put(ServerProperty.IS_REPLICATION_SECURE,
1150        Boolean.valueOf(replicationSecure));
1151  }
1152
1153  /**
1154   Updates the instance key public-key certificate value of this context from
1155   the local truststore of the instance bound by this context. Any current
1156   value of the certificate is overwritten. The intent of this method is to
1157   retrieve the instance-key public-key certificate when this context is bound
1158   to an instance, and cache it for later use in registering the instance into
1159   ADS.
1160   @param desc The map to update with the instance key-pair public-key
1161   certificate.
1162   @param ctx The bound server instance.
1163   @throws NamingException if unable to retrieve certificate from bound
1164   instance.
1165   */
1166  private static void updatePublicKeyCertificate(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException
1167  {
1168    /* TODO: this DN is declared in some core constants file. Create a constants
1169       file for the installer and import it into the core. */
1170    final String dnStr = "ds-cfg-key-id=ads-certificate,cn=ads-truststore";
1171    final LdapName dn = new LdapName(dnStr);
1172    for (int i = 0; i < 2 ; ++i) {
1173      /* If the entry does not exist in the instance's truststore backend, add
1174         it (which induces the CryptoManager to create the public-key
1175         certificate attribute), then repeat the search. */
1176      try {
1177        final SearchControls searchControls = new SearchControls();
1178        searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
1179        final String attrIDs[] = { "ds-cfg-public-key-certificate;binary" };
1180        searchControls.setReturningAttributes(attrIDs);
1181        final SearchResult certEntry = ctx.search(dn,
1182                   "(objectclass=ds-cfg-instance-key)", searchControls).next();
1183        final Attribute certAttr = certEntry.getAttributes().get(attrIDs[0]);
1184        if (null != certAttr) {
1185          /* attribute ds-cfg-public-key-certificate is a MUST in the schema */
1186          desc.serverProperties.put(
1187                  ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE,
1188                  certAttr.get());
1189        }
1190        break;
1191      }
1192      catch (NameNotFoundException x) {
1193        if (0 == i) {
1194          // Poke CryptoManager to initialize truststore. Note the special attribute in the request.
1195          final Attributes attrs = new BasicAttributes();
1196          final Attribute oc = new BasicAttribute("objectclass");
1197          oc.add("top");
1198          oc.add("ds-cfg-self-signed-cert-request");
1199          attrs.put(oc);
1200          ctx.createSubcontext(dn, attrs).close();
1201        }
1202        else {
1203          throw x;
1204        }
1205      }
1206    }
1207  }
1208
1209  private static void updateMiscellaneous(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException
1210  {
1211    SearchControls ctls = new SearchControls();
1212    ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
1213    ctls.setReturningAttributes(
1214        new String[] {
1215            "ds-sync-generation-id"
1216        });
1217    String filter = "(|(objectclass=*)(objectclass=ldapsubentry))";
1218
1219    LdapName jndiName = new LdapName("cn=schema");
1220    NamingEnumeration<SearchResult> listeners =
1221      ctx.search(jndiName, filter, ctls);
1222
1223    try
1224    {
1225      while(listeners.hasMore())
1226      {
1227        SearchResult sr = listeners.next();
1228
1229        desc.serverProperties.put(ServerProperty.SCHEMA_GENERATION_ID,
1230            getFirstValue(sr, "ds-sync-generation-id"));
1231      }
1232    }
1233    finally
1234    {
1235      listeners.close();
1236    }
1237  }
1238
1239  /**
1240   Seeds the bound instance's local ads-truststore with a set of instance
1241   key-pair public key certificates. The result is the instance will trust any
1242   instance possessing the private key corresponding to one of the public-key
1243   certificates. This trust is necessary at least to initialize replication,
1244   which uses the trusted certificate entries in the ads-truststore for server
1245   authentication.
1246   @param ctx The bound instance.
1247   @param keyEntryMap The set of valid (i.e., not tagged as compromised)
1248   instance key-pair public-key certificate entries in ADS represented as a map
1249   from keyID to public-key certificate (binary).
1250   @throws NamingException in case an error occurs while updating the instance's
1251   ads-truststore via LDAP.
1252   */
1253  public static void seedAdsTrustStore(
1254          InitialLdapContext ctx,
1255          Map<String, byte[]> keyEntryMap)
1256          throws NamingException
1257  {
1258    /* TODO: this DN is declared in some core constants file. Create a
1259       constants file for the installer and import it into the core. */
1260    final Attribute oc = new BasicAttribute("objectclass");
1261    oc.add("top");
1262    oc.add("ds-cfg-instance-key");
1263    for (Map.Entry<String, byte[]> keyEntry : keyEntryMap.entrySet()){
1264      final BasicAttributes keyAttrs = new BasicAttributes();
1265      keyAttrs.put(oc);
1266      final Attribute rdnAttr = new BasicAttribute(
1267              ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName(),
1268              keyEntry.getKey());
1269      keyAttrs.put(rdnAttr);
1270      keyAttrs.put(new BasicAttribute(
1271              ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE.
1272                      getAttributeName() + ";binary", keyEntry.getValue()));
1273      final LdapName keyDn = new LdapName(rdnAttr.getID() + "=" + Rdn.escapeValue(rdnAttr.get()) + "," + TRUSTSTORE_DN);
1274      try {
1275        ctx.createSubcontext(keyDn, keyAttrs).close();
1276      }
1277      catch(NameAlreadyBoundException x){
1278        ctx.destroySubcontext(keyDn);
1279        ctx.createSubcontext(keyDn, keyAttrs).close();
1280      }
1281    }
1282  }
1283
1284  /**
1285   * Cleans up the contents of the ads truststore.
1286   *
1287   * @param ctx the bound instance.
1288   * @throws NamingException in case an error occurs while updating the
1289   * instance's ads-truststore via LDAP.
1290   */
1291  public static void cleanAdsTrustStore(InitialLdapContext ctx)
1292  throws NamingException
1293  {
1294    try
1295    {
1296      SearchControls sc = new SearchControls();
1297      sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1298      sc.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES });
1299      NamingEnumeration<SearchResult> ne = ctx.search(TRUSTSTORE_DN,
1300          "(objectclass=ds-cfg-instance-key)", sc);
1301      ArrayList<String> dnsToDelete = new ArrayList<>();
1302      try
1303      {
1304        while (ne.hasMore())
1305        {
1306          SearchResult sr = ne.next();
1307          dnsToDelete.add(sr.getName()+","+TRUSTSTORE_DN);
1308        }
1309      }
1310      finally
1311      {
1312        ne.close();
1313      }
1314      for (String dn : dnsToDelete)
1315      {
1316        ctx.destroySubcontext(dn);
1317      }
1318    }
1319    catch (NameNotFoundException nnfe)
1320    {
1321      // Ignore
1322      logger.warn(LocalizableMessage.raw("Error cleaning truststore: "+nnfe, nnfe));
1323    }
1324  }
1325
1326  /**
1327   * Returns the values of the ds-base-dn-entry count attributes for the given
1328   * backend monitor entry using the provided InitialLdapContext.
1329   * @param ctx the InitialLdapContext to use to update the configuration.
1330   * @param backendID the id of the backend.
1331   * @return the values of the ds-base-dn-entry count attribute.
1332   * @throws NamingException if there was an error.
1333   */
1334  private static Set<String> getBaseDNEntryCount(InitialLdapContext ctx,
1335      String backendID) throws NamingException
1336  {
1337    LinkedHashSet<String> v = new LinkedHashSet<>();
1338    SearchControls ctls = new SearchControls();
1339    ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1340    ctls.setReturningAttributes(
1341        new String[] {
1342            "ds-base-dn-entry-count"
1343        });
1344    String filter = "(ds-backend-id="+backendID+")";
1345
1346    LdapName jndiName = new LdapName("cn=monitor");
1347    NamingEnumeration<SearchResult> listeners =
1348      ctx.search(jndiName, filter, ctls);
1349
1350    try
1351    {
1352      while(listeners.hasMore())
1353      {
1354        SearchResult sr = listeners.next();
1355
1356        v.addAll(getValues(sr, "ds-base-dn-entry-count"));
1357      }
1358    }
1359    finally
1360    {
1361      listeners.close();
1362    }
1363    return v;
1364  }
1365
1366  /**
1367   * An convenience method to know if the provided ID corresponds to a
1368   * configuration backend or not.
1369   * @param id the backend ID to analyze
1370   * @return <CODE>true</CODE> if the the id corresponds to a configuration
1371   * backend and <CODE>false</CODE> otherwise.
1372   */
1373  private static boolean isConfigBackend(String id)
1374  {
1375    return "tasks".equalsIgnoreCase(id) ||
1376    "schema".equalsIgnoreCase(id) ||
1377    "config".equalsIgnoreCase(id) ||
1378    "monitor".equalsIgnoreCase(id) ||
1379    "backup".equalsIgnoreCase(id) ||
1380    "ads-truststore".equalsIgnoreCase(id);
1381  }
1382
1383  /**
1384   * An convenience method to know if the provided ID corresponds to the schema
1385   * backend or not.
1386   * @param id the backend ID to analyze
1387   * @return <CODE>true</CODE> if the the id corresponds to the schema backend
1388   * and <CODE>false</CODE> otherwise.
1389   */
1390  private static boolean isSchemaBackend(String id)
1391  {
1392    return "schema".equalsIgnoreCase(id);
1393  }
1394
1395  /**
1396   * Returns the replication server normalized String for a given host name
1397   * and replication port.
1398   * @param hostName the host name.
1399   * @param replicationPort the replication port.
1400   * @return the replication server normalized String for a given host name
1401   * and replication port.
1402   */
1403  public static String getReplicationServer(String hostName, int replicationPort)
1404  {
1405    return getServerRepresentation(hostName, replicationPort);
1406  }
1407
1408  /**
1409   * Returns the normalized server representation for a given host name and
1410   * port.
1411   * @param hostName the host name.
1412   * @param port the port.
1413   * @return the normalized server representation for a given host name and
1414   * port.
1415   */
1416  public static String getServerRepresentation(String hostName, int port)
1417  {
1418    return hostName.toLowerCase() + ":" + port;
1419  }
1420
1421  /**
1422   * Returns a representation of a base DN for a set of servers.
1423   * @param baseDN the base DN.
1424   * @param servers the servers.
1425   * @return a representation of a base DN for a set of servers.
1426   */
1427  public static String getSuffixDisplay(String baseDN,
1428      Set<ServerDescriptor> servers)
1429  {
1430    StringBuilder sb = new StringBuilder();
1431    sb.append(baseDN);
1432    for (ServerDescriptor server : servers)
1433    {
1434      sb.append(Constants.LINE_SEPARATOR).append("    ");
1435      sb.append(server.getHostPort(true));
1436    }
1437    return sb.toString();
1438  }
1439
1440  /**
1441   * Tells whether the provided server descriptor represents the same server
1442   * as this object.
1443   * @param server the server to make the comparison.
1444   * @return whether the provided server descriptor represents the same server
1445   * as this object or not.
1446   */
1447  public boolean isSameServer(ServerDescriptor server)
1448  {
1449    return getId().equals(server.getId());
1450  }
1451}