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