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 2012-2015 ForgeRock AS
026 */
027package org.opends.admin.ads;
028
029import static org.forgerock.util.Utils.*;
030import static org.opends.messages.QuickSetupMessages.*;
031
032import java.io.File;
033import java.util.HashMap;
034import java.util.HashSet;
035import java.util.LinkedHashSet;
036import java.util.LinkedList;
037import java.util.Map;
038import java.util.Set;
039import java.util.SortedSet;
040import java.util.TreeSet;
041
042import javax.naming.CompositeName;
043import javax.naming.InvalidNameException;
044import javax.naming.NameAlreadyBoundException;
045import javax.naming.NameNotFoundException;
046import javax.naming.NamingEnumeration;
047import javax.naming.NamingException;
048import javax.naming.NoPermissionException;
049import javax.naming.NotContextException;
050import javax.naming.directory.Attribute;
051import javax.naming.directory.Attributes;
052import javax.naming.directory.BasicAttribute;
053import javax.naming.directory.BasicAttributes;
054import javax.naming.directory.DirContext;
055import javax.naming.directory.SearchControls;
056import javax.naming.directory.SearchResult;
057import javax.naming.ldap.Control;
058import javax.naming.ldap.InitialLdapContext;
059import javax.naming.ldap.LdapContext;
060import javax.naming.ldap.LdapName;
061import javax.naming.ldap.Rdn;
062
063import org.forgerock.i18n.LocalizableMessage;
064import org.forgerock.i18n.slf4j.LocalizedLogger;
065import org.opends.admin.ads.ADSContextException.ErrorType;
066import org.opends.admin.ads.util.ConnectionUtils;
067import org.opends.quicksetup.Constants;
068import org.opends.server.schema.SchemaConstants;
069
070/** Class used to update and read the contents of the Administration Data. */
071public class ADSContext
072{
073  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
074
075  /**
076   * Enumeration containing the different server properties syntaxes that could
077   * be stored in the ADS.
078   */
079  public enum ADSPropertySyntax
080  {
081    /** String syntax. */
082    STRING,
083    /** Integer syntax. */
084    INTEGER,
085    /** Boolean syntax. */
086    BOOLEAN,
087    /** Certificate;binary syntax. */
088    CERTIFICATE_BINARY
089  }
090
091  /** Enumeration containing the different server properties that are stored in the ADS. */
092  public enum ServerProperty
093  {
094    /** The ID used to identify the server. */
095    ID("id",ADSPropertySyntax.STRING),
096    /** The host name of the server. */
097    HOST_NAME("hostname",ADSPropertySyntax.STRING),
098    /** The LDAP port of the server. */
099    LDAP_PORT("ldapport",ADSPropertySyntax.INTEGER),
100    /** The JMX port of the server. */
101    JMX_PORT("jmxport",ADSPropertySyntax.INTEGER),
102    /** The JMX secure port of the server. */
103    JMXS_PORT("jmxsport",ADSPropertySyntax.INTEGER),
104    /** The LDAPS port of the server. */
105    LDAPS_PORT("ldapsport",ADSPropertySyntax.INTEGER),
106    /** The administration connector port of the server. */
107    ADMIN_PORT("adminport",ADSPropertySyntax.INTEGER),
108    /** The certificate used by the server. */
109    CERTIFICATE("certificate",ADSPropertySyntax.STRING),
110    /** The path where the server is installed. */
111    INSTANCE_PATH("instancepath",ADSPropertySyntax.STRING),
112    /** The description of the server. */
113    DESCRIPTION("description",ADSPropertySyntax.STRING),
114    /** The OS of the machine where the server is installed. */
115    HOST_OS("os",ADSPropertySyntax.STRING),
116    /** Whether LDAP is enabled or not. */
117    LDAP_ENABLED("ldapEnabled",ADSPropertySyntax.BOOLEAN),
118    /** Whether LDAPS is enabled or not. */
119    LDAPS_ENABLED("ldapsEnabled",ADSPropertySyntax.BOOLEAN),
120    /** Whether ADMIN is enabled or not. */
121    ADMIN_ENABLED("adminEnabled",ADSPropertySyntax.BOOLEAN),
122    /** Whether StartTLS is enabled or not. */
123    STARTTLS_ENABLED("startTLSEnabled",ADSPropertySyntax.BOOLEAN),
124    /** Whether JMX is enabled or not. */
125    JMX_ENABLED("jmxEnabled",ADSPropertySyntax.BOOLEAN),
126    /** Whether JMX is enabled or not. */
127    JMXS_ENABLED("jmxsEnabled",ADSPropertySyntax.BOOLEAN),
128    /** The location of the server. */
129    LOCATION("location",ADSPropertySyntax.STRING),
130    /** The groups to which this server belongs. */
131    GROUPS("memberofgroups",ADSPropertySyntax.STRING),
132    /** The unique name of the instance key public-key certificate. */
133    INSTANCE_KEY_ID("ds-cfg-key-id",ADSPropertySyntax.STRING),
134    /**
135     * The instance key-pair public-key certificate. Note: This attribute
136     * belongs to an instance key entry, separate from the server entry and
137     * named by the ds-cfg-key-id attribute from the server entry.
138     */
139    INSTANCE_PUBLIC_KEY_CERTIFICATE("ds-cfg-public-key-certificate", ADSPropertySyntax.CERTIFICATE_BINARY);
140
141    private String attrName;
142    private ADSPropertySyntax attSyntax;
143
144    /**
145     * Private constructor.
146     *
147     * @param n
148     *          the name of the attribute.
149     * @param s
150     *          the name of the syntax.
151     */
152    private ServerProperty(String n, ADSPropertySyntax s)
153    {
154      attrName = n;
155      attSyntax = s;
156    }
157
158    /**
159     * Returns the attribute name.
160     *
161     * @return the attribute name.
162     */
163    public String getAttributeName()
164    {
165      return attrName;
166    }
167
168    /**
169     * Returns the attribute syntax.
170     *
171     * @return the attribute syntax.
172     */
173    public ADSPropertySyntax getAttributeSyntax()
174    {
175      return attSyntax;
176    }
177  }
178
179  /** Default global admin UID. */
180  public static final String GLOBAL_ADMIN_UID = "admin";
181
182  private static Map<String, ServerProperty> NAME_TO_SERVER_PROPERTY;
183
184  /**
185   * Get a ServerProperty associated to a name.
186   *
187   * @param name
188   *          The name of the property to retrieve.
189   * @return The corresponding ServerProperty or null if name doesn't match with
190   *         an existing property.
191   */
192  public static ServerProperty getServerPropFromName(String name)
193  {
194    if (NAME_TO_SERVER_PROPERTY == null)
195    {
196      NAME_TO_SERVER_PROPERTY = new HashMap<>();
197      for (ServerProperty s : ServerProperty.values())
198      {
199        NAME_TO_SERVER_PROPERTY.put(s.getAttributeName(), s);
200      }
201    }
202    return NAME_TO_SERVER_PROPERTY.get(name);
203  }
204
205  /** The list of server properties that are multivalued. */
206  private static final Set<ServerProperty> MULTIVALUED_SERVER_PROPERTIES = new HashSet<>();
207  static
208  {
209    MULTIVALUED_SERVER_PROPERTIES.add(ServerProperty.GROUPS);
210  }
211
212  /** The default server group which will contain all registered servers. */
213  public static final String ALL_SERVERGROUP_NAME = "all-servers";
214
215  /** Enumeration containing the different server group properties that are stored in the ADS. */
216  public enum ServerGroupProperty
217  {
218    /** The UID of the server group. */
219    UID("cn"),
220    /** The description of the server group. */
221    DESCRIPTION("description"),
222    /** The members of the server group. */
223    MEMBERS("uniqueMember");
224
225    private String attrName;
226
227    /**
228     * Private constructor.
229     *
230     * @param n
231     *          the attribute name.
232     */
233    private ServerGroupProperty(String n)
234    {
235      attrName = n;
236    }
237
238    /**
239     * Returns the attribute name.
240     *
241     * @return the attribute name.
242     */
243    public String getAttributeName()
244    {
245      return attrName;
246    }
247  }
248
249  /** The list of server group properties that are multivalued. */
250  private static final Set<ServerGroupProperty> MULTIVALUED_SERVER_GROUP_PROPERTIES = new HashSet<>();
251  static
252  {
253    MULTIVALUED_SERVER_GROUP_PROPERTIES.add(ServerGroupProperty.MEMBERS);
254  }
255
256  /** The enumeration containing the different Administrator properties. */
257  public enum AdministratorProperty
258  {
259    /** The UID of the administrator. */
260    UID("id", ADSPropertySyntax.STRING),
261    /** The password of the administrator. */
262    PASSWORD("password", ADSPropertySyntax.STRING),
263    /** The description of the administrator. */
264    DESCRIPTION("description", ADSPropertySyntax.STRING),
265    /** The DN of the administrator. */
266    ADMINISTRATOR_DN("administrator dn", ADSPropertySyntax.STRING),
267    /** The administrator privilege. */
268    PRIVILEGE("privilege", ADSPropertySyntax.STRING);
269
270    private String attrName;
271    private ADSPropertySyntax attrSyntax;
272
273    /**
274     * Private constructor.
275     *
276     * @param n
277     *          the name of the attribute.
278     * @param s
279     *          the name of the syntax.
280     */
281    private AdministratorProperty(String n, ADSPropertySyntax s)
282    {
283      attrName = n;
284      attrSyntax = s;
285    }
286
287    /**
288     * Returns the attribute name.
289     *
290     * @return the attribute name.
291     */
292    public String getAttributeName()
293    {
294      return attrName;
295    }
296
297    /**
298     * Returns the attribute syntax.
299     *
300     * @return the attribute syntax.
301     */
302    public ADSPropertySyntax getAttributeSyntax()
303    {
304      return attrSyntax;
305    }
306  }
307
308  private static HashMap<String, AdministratorProperty> nameToAdminUserProperty;
309
310  /**
311   * Get a AdministratorProperty associated to a name.
312   *
313   * @param name
314   *          The name of the property to retrieve.
315   * @return The corresponding AdministratorProperty or null if name doesn't
316   *         match with an existing property.
317   */
318  public static AdministratorProperty getAdminUserPropFromName(String name)
319  {
320    if (nameToAdminUserProperty == null)
321    {
322      nameToAdminUserProperty = new HashMap<>();
323      for (AdministratorProperty u : AdministratorProperty.values())
324      {
325        nameToAdminUserProperty.put(u.getAttributeName(), u);
326      }
327    }
328    return nameToAdminUserProperty.get(name);
329  }
330
331  /** The context used to retrieve information. */
332  private final InitialLdapContext dirContext;
333
334  /**
335   * Constructor of the ADSContext.
336   *
337   * @param dirContext
338   *          the DirContext that must be used to retrieve information.
339   */
340  public ADSContext(InitialLdapContext dirContext)
341  {
342    this.dirContext = dirContext;
343  }
344
345  /**
346   * Returns the DirContext used to retrieve information by this ADSContext.
347   *
348   * @return the DirContext used to retrieve information by this ADSContext.
349   */
350  public InitialLdapContext getDirContext()
351  {
352    return dirContext;
353  }
354
355  /**
356   * Method called to register a server in the ADS.
357   *
358   * @param serverProperties
359   *          the properties of the server.
360   * @throws ADSContextException
361   *           if the server could not be registered.
362   */
363  public void registerServer(Map<ServerProperty, Object> serverProperties) throws ADSContextException
364  {
365    LdapName dn = makeDNFromServerProperties(serverProperties);
366    BasicAttributes attrs = makeAttrsFromServerProperties(serverProperties, true);
367    try
368    {
369      // This check is required because by default the server container entry
370      // does not exist.
371      if (!isExistingEntry(nameFromDN(getServerContainerDN())))
372      {
373        createContainerEntry(getServerContainerDN());
374      }
375      dirContext.createSubcontext(dn, attrs).close();
376      if (serverProperties.containsKey(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE))
377      {
378        registerInstanceKeyCertificate(serverProperties, dn);
379      }
380
381      // register this server into "all" groups
382      Map<ServerGroupProperty, Object> serverGroupProperties = new HashMap<>();
383      Set<String> memberList = getServerGroupMemberList(ALL_SERVERGROUP_NAME);
384      if (memberList == null)
385      {
386        memberList = new HashSet<>();
387      }
388      String newMember = "cn=" + Rdn.escapeValue(serverProperties.get(ServerProperty.ID));
389
390      memberList.add(newMember);
391      serverGroupProperties.put(ServerGroupProperty.MEMBERS, memberList);
392
393      updateServerGroup(ALL_SERVERGROUP_NAME, serverGroupProperties);
394
395      // Update the server property "GROUPS"
396      Set<?> rawGroupList = (Set<?>) serverProperties.get(ServerProperty.GROUPS);
397      Set<String> groupList = new HashSet<>();
398      if (rawGroupList != null)
399      {
400        for (Object elm : rawGroupList)
401        {
402          groupList.add(elm.toString());
403        }
404      }
405      groupList.add(ALL_SERVERGROUP_NAME);
406      serverProperties.put(ServerProperty.GROUPS, groupList);
407      updateServer(serverProperties, null);
408    }
409    catch (ADSContextException ace)
410    {
411      throw ace;
412    }
413    catch (NameAlreadyBoundException x)
414    {
415      throw new ADSContextException(ErrorType.ALREADY_REGISTERED);
416    }
417    catch (Exception x)
418    {
419      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
420    }
421  }
422
423  /**
424   * Method called to update the properties of a server in the ADS.
425   *
426   * @param serverProperties
427   *          the new properties of the server.
428   * @param newServerId
429   *          The new server Identifier, or null.
430   * @throws ADSContextException
431   *           if the server could not be registered.
432   */
433  public void updateServer(Map<ServerProperty, Object> serverProperties, String newServerId) throws ADSContextException
434  {
435    LdapName dn = makeDNFromServerProperties(serverProperties);
436
437    try
438    {
439      if (newServerId != null)
440      {
441        Map<ServerProperty, Object> newServerProps = new HashMap<>(serverProperties);
442        newServerProps.put(ServerProperty.ID, newServerId);
443        LdapName newDn = makeDNFromServerProperties(newServerProps);
444        dirContext.rename(dn, newDn);
445        dn = newDn;
446        serverProperties.put(ServerProperty.ID, newServerId);
447      }
448      BasicAttributes attrs = makeAttrsFromServerProperties(serverProperties, false);
449      dirContext.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
450      if (serverProperties.containsKey(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE))
451      {
452        registerInstanceKeyCertificate(serverProperties, dn);
453      }
454    }
455    catch (ADSContextException ace)
456    {
457      throw ace;
458    }
459    catch (NameNotFoundException x)
460    {
461      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
462    }
463    catch (Exception x)
464    {
465      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
466    }
467  }
468
469  /**
470   * Method called to unregister a server in the ADS. Note that the server's
471   * instance key-pair public-key certificate entry (created in
472   * <tt>registerServer()</tt>) is left untouched.
473   *
474   * @param serverProperties
475   *          the properties of the server.
476   * @throws ADSContextException
477   *           if the server could not be unregistered.
478   */
479  public void unregisterServer(Map<ServerProperty, Object> serverProperties) throws ADSContextException
480  {
481    LdapName dn = makeDNFromServerProperties(serverProperties);
482    try
483    {
484      // Unregister the server from the server groups.
485      String member = "cn=" + Rdn.escapeValue(serverProperties.get(ServerProperty.ID));
486      Set<Map<ServerGroupProperty, Object>> serverGroups = readServerGroupRegistry();
487      for (Map<ServerGroupProperty, Object> serverGroup : serverGroups)
488      {
489        Set<?> memberList = (Set<?>) serverGroup.get(ServerGroupProperty.MEMBERS);
490        if (memberList != null && memberList.remove(member))
491        {
492          Map<ServerGroupProperty, Object> serverGroupProperties = new HashMap<>();
493          serverGroupProperties.put(ServerGroupProperty.MEMBERS, memberList);
494          String groupName = (String) serverGroup.get(ServerGroupProperty.UID);
495          updateServerGroup(groupName, serverGroupProperties);
496        }
497      }
498
499      dirContext.destroySubcontext(dn);
500    }
501    catch (NameNotFoundException x)
502    {
503      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
504    }
505    catch (NamingException x)
506    {
507      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
508    }
509
510    // Unregister the server in server groups
511    NamingEnumeration<SearchResult> ne = null;
512    try
513    {
514      SearchControls sc = new SearchControls();
515
516      String serverID = getServerID(serverProperties);
517      if (serverID != null)
518      {
519        String memberAttrName = ServerGroupProperty.MEMBERS.getAttributeName();
520        String filter = "(" + memberAttrName + "=cn=" + serverID + ")";
521        sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
522        ne = dirContext.search(getServerGroupContainerDN(), filter, sc);
523        while (ne.hasMore())
524        {
525          SearchResult sr = ne.next();
526          String groupDn = sr.getNameInNamespace();
527          BasicAttribute newAttr = new BasicAttribute(memberAttrName);
528          NamingEnumeration<? extends Attribute> attrs = sr.getAttributes().getAll();
529          try
530          {
531            while (attrs.hasMore())
532            {
533              Attribute attr = attrs.next();
534              String attrID = attr.getID();
535
536              if (attrID.equalsIgnoreCase(memberAttrName))
537              {
538                NamingEnumeration<?> ae = attr.getAll();
539                try
540                {
541                  while (ae.hasMore())
542                  {
543                    String value = (String) ae.next();
544                    if (!value.equalsIgnoreCase("cn=" + serverID))
545                    {
546                      newAttr.add(value);
547                    }
548                  }
549                }
550                finally
551                {
552                  handleCloseNamingEnumeration(ae);
553                }
554              }
555            }
556          }
557          finally
558          {
559            handleCloseNamingEnumeration(attrs);
560          }
561          BasicAttributes newAttrs = new BasicAttributes();
562          newAttrs.put(newAttr);
563          if (newAttr.size() > 0)
564          {
565            dirContext.modifyAttributes(groupDn, DirContext.REPLACE_ATTRIBUTE, newAttrs);
566          }
567          else
568          {
569            dirContext.modifyAttributes(groupDn, DirContext.REMOVE_ATTRIBUTE, newAttrs);
570          }
571        }
572      }
573    }
574    catch (NameNotFoundException x)
575    {
576      throw new ADSContextException(ErrorType.BROKEN_INSTALL);
577    }
578    catch (NoPermissionException x)
579    {
580      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
581    }
582    catch (NamingException x)
583    {
584      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
585    }
586    finally
587    {
588      handleCloseNamingEnumeration(ne);
589    }
590  }
591
592  /**
593   * Returns whether a given server is already registered or not.
594   *
595   * @param serverProperties
596   *          the server properties.
597   * @return <CODE>true</CODE> if the server was registered and
598   *         <CODE>false</CODE> otherwise.
599   * @throws ADSContextException
600   *           if something went wrong.
601   */
602  public boolean isServerAlreadyRegistered(Map<ServerProperty, Object> serverProperties) throws ADSContextException
603  {
604    return isExistingEntry(makeDNFromServerProperties(serverProperties));
605  }
606
607  /**
608   * Returns whether a given administrator is already registered or not.
609   *
610   * @param uid
611   *          the administrator UID.
612   * @return <CODE>true</CODE> if the administrator was registered and
613   *         <CODE>false</CODE> otherwise.
614   * @throws ADSContextException
615   *           if something went wrong.
616   */
617  public boolean isAdministratorAlreadyRegistered(String uid) throws ADSContextException
618  {
619    return isExistingEntry(makeDNFromAdministratorProperties(uid));
620  }
621
622  /**
623   * A convenience method that takes some server properties as parameter and if
624   * there is no server registered associated with those properties, registers
625   * it and if it is already registered, updates it.
626   *
627   * @param serverProperties
628   *          the server properties.
629   * @return 0 if the server was registered; 1 if updated (i.e., the server
630   *         entry was already in ADS).
631   * @throws ADSContextException
632   *           if something goes wrong.
633   */
634  public int registerOrUpdateServer(Map<ServerProperty, Object> serverProperties) throws ADSContextException
635  {
636    try
637    {
638      registerServer(serverProperties);
639      return 0;
640    }
641    catch (ADSContextException x)
642    {
643      if (x.getError() == ErrorType.ALREADY_REGISTERED)
644      {
645        updateServer(serverProperties, null);
646        return 1;
647      }
648
649      throw x;
650    }
651  }
652
653  /**
654   * Returns the member list of a group of server.
655   *
656   * @param serverGroupId
657   *          The group name.
658   * @return the member list of a group of server.
659   * @throws ADSContextException
660   *           if something goes wrong.
661   */
662  public Set<String> getServerGroupMemberList(String serverGroupId) throws ADSContextException
663  {
664    LdapName dn = nameFromDN("cn=" + Rdn.escapeValue(serverGroupId) + "," + getServerGroupContainerDN());
665
666    Set<String> result = new HashSet<>();
667    NamingEnumeration<SearchResult> srs = null;
668    NamingEnumeration<? extends Attribute> ne = null;
669    try
670    {
671      SearchControls sc = new SearchControls();
672      sc.setSearchScope(SearchControls.OBJECT_SCOPE);
673      srs = getDirContext().search(dn, "(objectclass=*)", sc);
674
675      if (!srs.hasMore())
676      {
677        return result;
678      }
679      Attributes attrs = srs.next().getAttributes();
680      ne = attrs.getAll();
681      while (ne.hasMore())
682      {
683        Attribute attr = ne.next();
684        String attrID = attr.getID();
685
686        if (!attrID.toLowerCase().equals(ServerGroupProperty.MEMBERS.getAttributeName().toLowerCase()))
687        {
688          continue;
689        }
690
691        // We have the members list
692        NamingEnumeration<?> ae = attr.getAll();
693        try
694        {
695          while (ae.hasMore())
696          {
697            result.add((String) ae.next());
698          }
699        }
700        finally
701        {
702          handleCloseNamingEnumeration(ae);
703        }
704        break;
705      }
706    }
707    catch (NameNotFoundException x)
708    {
709      result = new HashSet<>();
710    }
711    catch (NoPermissionException x)
712    {
713      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
714    }
715    catch (NamingException x)
716    {
717      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
718    }
719    finally
720    {
721      handleCloseNamingEnumeration(srs);
722      handleCloseNamingEnumeration(ne);
723    }
724    return result;
725  }
726
727  /**
728   * Returns a set containing the servers that are registered in the ADS.
729   *
730   * @return a set containing the servers that are registered in the ADS.
731   * @throws ADSContextException
732   *           if something goes wrong.
733   */
734  public Set<Map<ServerProperty, Object>> readServerRegistry() throws ADSContextException
735  {
736    Set<Map<ServerProperty, Object>> result = new HashSet<>();
737    NamingEnumeration<SearchResult> ne = null;
738    try
739    {
740      SearchControls sc = new SearchControls();
741
742      sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
743      ne = dirContext.search(getServerContainerDN(), "(objectclass=*)", sc);
744      while (ne.hasMore())
745      {
746        SearchResult sr = ne.next();
747        Map<ServerProperty, Object> properties = makePropertiesFromServerAttrs(sr.getAttributes());
748        Object keyId = properties.get(ServerProperty.INSTANCE_KEY_ID);
749        if (keyId != null)
750        {
751          NamingEnumeration<SearchResult> ne2 = null;
752          try
753          {
754            SearchControls sc1 = new SearchControls();
755            sc1.setSearchScope(SearchControls.ONELEVEL_SCOPE);
756            final String attrIDs[] = { "ds-cfg-public-key-certificate;binary" };
757            sc1.setReturningAttributes(attrIDs);
758
759            ne2 = dirContext.search(getInstanceKeysContainerDN(), "(ds-cfg-key-id=" + keyId + ")", sc);
760            boolean found = false;
761            while (ne2.hasMore())
762            {
763              SearchResult certEntry = ne2.next();
764              Attribute certAttr = certEntry.getAttributes().get(attrIDs[0]);
765              properties.put(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE, certAttr.get());
766              found = true;
767            }
768            if (!found)
769            {
770              logger.warn(LocalizableMessage.raw("Could not find public key for " + properties));
771            }
772          }
773          catch (NameNotFoundException x)
774          {
775            logger.warn(LocalizableMessage.raw("Could not find public key for " + properties));
776          }
777          finally
778          {
779            handleCloseNamingEnumeration(ne2);
780          }
781        }
782        result.add(properties);
783      }
784    }
785    catch (NameNotFoundException x)
786    {
787      throw new ADSContextException(ErrorType.BROKEN_INSTALL);
788    }
789    catch (NoPermissionException x)
790    {
791      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
792    }
793    catch (NamingException x)
794    {
795      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
796    }
797    finally
798    {
799      handleCloseNamingEnumeration(ne);
800    }
801
802    return result;
803  }
804
805  /**
806   * Creates a Server Group in the ADS.
807   *
808   * @param serverGroupProperties
809   *          the properties of the server group to be created.
810   * @throws ADSContextException
811   *           if something goes wrong.
812   */
813  public void createServerGroup(Map<ServerGroupProperty, Object> serverGroupProperties) throws ADSContextException
814  {
815    LdapName dn = makeDNFromServerGroupProperties(serverGroupProperties);
816    BasicAttributes attrs = makeAttrsFromServerGroupProperties(serverGroupProperties);
817    // Add the objectclass attribute value
818    Attribute oc = new BasicAttribute("objectclass");
819    oc.add("top");
820    oc.add("groupOfUniqueNames");
821    attrs.put(oc);
822    try
823    {
824      DirContext ctx = dirContext.createSubcontext(dn, attrs);
825      ctx.close();
826    }
827    catch (NameAlreadyBoundException x)
828    {
829      throw new ADSContextException(ErrorType.ALREADY_REGISTERED);
830    }
831    catch (NamingException x)
832    {
833      throw new ADSContextException(ErrorType.BROKEN_INSTALL, x);
834    }
835  }
836
837  /**
838   * Updates the properties of a Server Group in the ADS.
839   *
840   * @param serverGroupProperties
841   *          the new properties of the server group to be updated.
842   * @param groupID
843   *          The group name.
844   * @throws ADSContextException
845   *           if something goes wrong.
846   */
847  public void updateServerGroup(String groupID, Map<ServerGroupProperty, Object> serverGroupProperties)
848      throws ADSContextException
849  {
850    LdapName dn = nameFromDN("cn=" + Rdn.escapeValue(groupID) + "," + getServerGroupContainerDN());
851    try
852    {
853      // Entry renaming ?
854      if (serverGroupProperties.containsKey(ServerGroupProperty.UID))
855      {
856        String newGroupId = serverGroupProperties.get(ServerGroupProperty.UID).toString();
857        if (!newGroupId.equals(groupID))
858        {
859          // Rename to entry
860          LdapName newDN = nameFromDN("cn=" + Rdn.escapeValue(newGroupId) + "," + getServerGroupContainerDN());
861          dirContext.rename(dn, newDN);
862          dn = newDN;
863        }
864
865        // In any case, we remove the "cn" attribute.
866        serverGroupProperties.remove(ServerGroupProperty.UID);
867      }
868      if (serverGroupProperties.isEmpty())
869      {
870        return;
871      }
872
873      BasicAttributes attrs = makeAttrsFromServerGroupProperties(serverGroupProperties);
874      // attribute modification
875      dirContext.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
876    }
877    catch (NameNotFoundException x)
878    {
879      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
880    }
881    catch (NameAlreadyBoundException x)
882    {
883      throw new ADSContextException(ErrorType.ALREADY_REGISTERED);
884    }
885    catch (NamingException x)
886    {
887      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
888    }
889  }
890
891  /**
892   * Updates the properties of a Server Group in the ADS.
893   *
894   * @param serverGroupProperties
895   *          the new properties of the server group to be updated.
896   * @param groupID
897   *          The group name.
898   * @throws ADSContextException
899   *           if something goes wrong.
900   */
901  public void removeServerGroupProp(String groupID, Set<ServerGroupProperty> serverGroupProperties)
902      throws ADSContextException
903  {
904    LdapName dn = nameFromDN("cn=" + Rdn.escapeValue(groupID) + "," + getServerGroupContainerDN());
905    BasicAttributes attrs = makeAttrsFromServerGroupProperties(serverGroupProperties);
906    try
907    {
908      dirContext.modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, attrs);
909    }
910    catch (NameAlreadyBoundException x)
911    {
912      throw new ADSContextException(ErrorType.ALREADY_REGISTERED);
913    }
914    catch (NamingException x)
915    {
916      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
917    }
918  }
919
920  /**
921   * Deletes a Server Group in the ADS.
922   *
923   * @param serverGroupProperties
924   *          the properties of the server group to be deleted.
925   * @throws ADSContextException
926   *           if something goes wrong.
927   */
928  public void deleteServerGroup(Map<ServerGroupProperty, Object> serverGroupProperties) throws ADSContextException
929  {
930    LdapName dn = makeDNFromServerGroupProperties(serverGroupProperties);
931    try
932    {
933      dirContext.destroySubcontext(dn);
934    }
935    catch (NamingException x)
936    {
937      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
938    }
939  }
940
941  /**
942   * Returns a set containing the server groups that are defined in the ADS.
943   *
944   * @return a set containing the server groups that are defined in the ADS.
945   * @throws ADSContextException
946   *           if something goes wrong.
947   */
948  public Set<Map<ServerGroupProperty, Object>> readServerGroupRegistry() throws ADSContextException
949  {
950    Set<Map<ServerGroupProperty, Object>> result = new HashSet<>();
951    NamingEnumeration<SearchResult> ne = null;
952    try
953    {
954      SearchControls sc = new SearchControls();
955
956      sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
957      ne = dirContext.search(getServerGroupContainerDN(), "(objectclass=*)", sc);
958      while (ne.hasMore())
959      {
960        SearchResult sr = ne.next();
961        Map<ServerGroupProperty, Object> properties = makePropertiesFromServerGroupAttrs(sr.getAttributes());
962        result.add(properties);
963      }
964    }
965    catch (NameNotFoundException x)
966    {
967      throw new ADSContextException(ErrorType.BROKEN_INSTALL);
968    }
969    catch (NoPermissionException x)
970    {
971      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
972    }
973    catch (NamingException x)
974    {
975      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
976    }
977    finally
978    {
979      handleCloseNamingEnumeration(ne);
980    }
981    return result;
982  }
983
984  /**
985   * Returns a set containing the administrators that are defined in the ADS.
986   *
987   * @return a set containing the administrators that are defined in the ADS.
988   * @throws ADSContextException
989   *           if something goes wrong.
990   */
991  public Set<Map<AdministratorProperty, Object>> readAdministratorRegistry() throws ADSContextException
992  {
993    Set<Map<AdministratorProperty, Object>> result = new HashSet<>();
994    NamingEnumeration<SearchResult> ne = null;
995    try
996    {
997      SearchControls sc = new SearchControls();
998
999      sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1000      String[] attList = { "cn", "userpassword", "ds-privilege-name", "description" };
1001      sc.setReturningAttributes(attList);
1002      ne = dirContext.search(getAdministratorContainerDN(), "(objectclass=*)", sc);
1003      while (ne.hasMore())
1004      {
1005        SearchResult sr = ne.next();
1006        Map<AdministratorProperty, Object> properties =
1007            makePropertiesFromAdministratorAttrs(getRdn(sr.getName()), sr.getAttributes());
1008        result.add(properties);
1009      }
1010    }
1011    catch (NameNotFoundException x)
1012    {
1013      throw new ADSContextException(ErrorType.BROKEN_INSTALL);
1014    }
1015    catch (NoPermissionException x)
1016    {
1017      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1018    }
1019    catch (NamingException x)
1020    {
1021      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1022    }
1023    finally
1024    {
1025      handleCloseNamingEnumeration(ne);
1026    }
1027
1028    return result;
1029  }
1030
1031  /**
1032   * Creates the Administration Data in the server. The call to this method
1033   * assumes that OpenDS.jar has already been loaded. So this should not be
1034   * called by the Java Web Start before being sure that this jar is loaded.
1035   *
1036   * @param backendName
1037   *          the backend name which will handle admin information.
1038   *          <CODE>null</CODE> to use the default backend name for the admin
1039   *          information.
1040   * @throws ADSContextException
1041   *           if something goes wrong.
1042   */
1043  public void createAdminData(String backendName) throws ADSContextException
1044  {
1045    // Add the administration suffix
1046    createAdministrationSuffix(backendName);
1047    createAdminDataContainers();
1048  }
1049
1050  /** Create container entries. */
1051  private void createAdminDataContainers() throws ADSContextException
1052  {
1053    // Create the DIT below the administration suffix
1054    if (!isExistingEntry(nameFromDN(getAdministrationSuffixDN())))
1055    {
1056      createTopContainerEntry();
1057    }
1058    if (!isExistingEntry(nameFromDN(getAdministratorContainerDN())))
1059    {
1060      createAdministratorContainerEntry();
1061    }
1062    if (!isExistingEntry(nameFromDN(getServerContainerDN())))
1063    {
1064      createContainerEntry(getServerContainerDN());
1065    }
1066    if (!isExistingEntry(nameFromDN(getServerGroupContainerDN())))
1067    {
1068      createContainerEntry(getServerGroupContainerDN());
1069    }
1070
1071    // Add the default "all-servers" group
1072    if (!isExistingEntry(nameFromDN(getAllServerGroupDN())))
1073    {
1074      Map<ServerGroupProperty, Object> allServersGroupsMap = new HashMap<>();
1075      allServersGroupsMap.put(ServerGroupProperty.UID, ALL_SERVERGROUP_NAME);
1076      createServerGroup(allServersGroupsMap);
1077    }
1078
1079    // Create the CryptoManager instance key DIT below the administration suffix
1080    if (!isExistingEntry(nameFromDN(getInstanceKeysContainerDN())))
1081    {
1082      createContainerEntry(getInstanceKeysContainerDN());
1083    }
1084
1085    // Create the CryptoManager secret key DIT below the administration suffix
1086    if (!isExistingEntry(nameFromDN(getSecretKeysContainerDN())))
1087    {
1088      createContainerEntry(getSecretKeysContainerDN());
1089    }
1090  }
1091
1092  /**
1093   * Removes the administration data.
1094   *
1095   * @param removeAdministrators
1096   *          {@code true} if administrators should be removed. It may not be
1097   *          possible to remove administrators if the operation is being
1098   *          performed by one of the administrators because it will cause the
1099   *          administrator to be disconnected.
1100   * @throws ADSContextException
1101   *           if something goes wrong.
1102   */
1103  public void removeAdminData(boolean removeAdministrators) throws ADSContextException
1104  {
1105    String[] dns = { getServerContainerDN(), getServerGroupContainerDN(),
1106      removeAdministrators ? getAdministratorContainerDN() : null };
1107    try
1108    {
1109      Control[] controls = new Control[] { new SubtreeDeleteControl() };
1110      LdapContext tmpContext = dirContext.newInstance(controls);
1111      try
1112      {
1113        for (String dn : dns)
1114        {
1115          if (dn != null)
1116          {
1117            LdapName ldapName = nameFromDN(dn);
1118            if (isExistingEntry(ldapName))
1119            {
1120              tmpContext.destroySubcontext(dn);
1121            }
1122          }
1123        }
1124      }
1125      finally
1126      {
1127        try
1128        {
1129          tmpContext.close();
1130        }
1131        catch (Exception ex)
1132        {
1133          logger.warn(LocalizableMessage.raw("Error while closing LDAP connection after removing admin data", ex));
1134        }
1135      }
1136      // Recreate the container entries:
1137      createAdminDataContainers();
1138    }
1139    catch (NamingException x)
1140    {
1141      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1142    }
1143  }
1144
1145  /**
1146   * Returns <CODE>true</CODE> if the server contains Administration Data and
1147   * <CODE>false</CODE> otherwise.
1148   *
1149   * @return <CODE>true</CODE> if the server contains Administration Data and
1150   *         <CODE>false</CODE> otherwise.
1151   * @throws ADSContextException
1152   *           if something goes wrong.
1153   */
1154  public boolean hasAdminData() throws ADSContextException
1155  {
1156    String[] dns = { getAdministratorContainerDN(), getAllServerGroupDN(), getServerContainerDN(),
1157      getInstanceKeysContainerDN(), getSecretKeysContainerDN() };
1158    boolean hasAdminData = true;
1159    for (int i = 0; i < dns.length && hasAdminData; i++)
1160    {
1161      hasAdminData = isExistingEntry(nameFromDN(dns[i]));
1162    }
1163    return hasAdminData;
1164  }
1165
1166  /**
1167   * Returns the DN of the administrator for a given UID.
1168   *
1169   * @param uid
1170   *          the UID to be used to generate the DN.
1171   * @return the DN of the administrator for the given UID:
1172   */
1173  public static String getAdministratorDN(String uid)
1174  {
1175    return "cn=" + Rdn.escapeValue(uid) + "," + getAdministratorContainerDN();
1176  }
1177
1178  /**
1179   * Creates an Administrator in the ADS.
1180   *
1181   * @param adminProperties
1182   *          the properties of the administrator to be created.
1183   * @throws ADSContextException
1184   *           if something goes wrong.
1185   */
1186  public void createAdministrator(Map<AdministratorProperty, Object> adminProperties) throws ADSContextException
1187  {
1188    LdapName dnCentralAdmin = makeDNFromAdministratorProperties(adminProperties);
1189    BasicAttributes attrs = makeAttrsFromAdministratorProperties(adminProperties, true, null);
1190
1191    try
1192    {
1193      DirContext ctx = dirContext.createSubcontext(dnCentralAdmin, attrs);
1194      ctx.close();
1195    }
1196    catch (NameAlreadyBoundException x)
1197    {
1198      throw new ADSContextException(ErrorType.ALREADY_REGISTERED);
1199    }
1200    catch (NoPermissionException x)
1201    {
1202      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1203    }
1204    catch (NamingException x)
1205    {
1206      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1207    }
1208  }
1209
1210  /**
1211   * Deletes the administrator in the ADS.
1212   *
1213   * @param adminProperties
1214   *          the properties of the administrator to be deleted.
1215   * @throws ADSContextException
1216   *           if something goes wrong.
1217   */
1218  public void deleteAdministrator(Map<AdministratorProperty, Object> adminProperties) throws ADSContextException
1219  {
1220    LdapName dnCentralAdmin = makeDNFromAdministratorProperties(adminProperties);
1221
1222    try
1223    {
1224      dirContext.destroySubcontext(dnCentralAdmin);
1225    }
1226    catch (NameNotFoundException | NotContextException x)
1227    {
1228      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
1229    }
1230    catch (NoPermissionException x)
1231    {
1232      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1233    }
1234    catch (NamingException x)
1235    {
1236      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1237    }
1238  }
1239
1240  /**
1241   * Updates and administrator registered in the ADS.
1242   *
1243   * @param adminProperties
1244   *          the new properties of the administrator.
1245   * @param newAdminUserId
1246   *          The new admin user Identifier, or null.
1247   * @throws ADSContextException
1248   *           if something goes wrong.
1249   */
1250  public void updateAdministrator(Map<AdministratorProperty, Object> adminProperties, String newAdminUserId)
1251      throws ADSContextException
1252  {
1253    LdapName dnCentralAdmin = makeDNFromAdministratorProperties(adminProperties);
1254
1255    boolean updatePassword = adminProperties.containsKey(AdministratorProperty.PASSWORD);
1256
1257    NamingEnumeration<?> currentPrivileges = null;
1258    try
1259    {
1260      // Entry renaming
1261      if (newAdminUserId != null)
1262      {
1263        Map<AdministratorProperty, Object> newAdminUserProps = new HashMap<>(adminProperties);
1264        newAdminUserProps.put(AdministratorProperty.UID, newAdminUserId);
1265        LdapName newDn = makeDNFromAdministratorProperties(newAdminUserProps);
1266        dirContext.rename(dnCentralAdmin, newDn);
1267        dnCentralAdmin = newDn;
1268        adminProperties.put(AdministratorProperty.UID, newAdminUserId);
1269      }
1270
1271      // if modification includes 'privilege', we have to get first the
1272      // current privileges list.
1273      if (adminProperties.containsKey(AdministratorProperty.PRIVILEGE))
1274      {
1275        SearchControls sc = new SearchControls();
1276        sc.setSearchScope(SearchControls.OBJECT_SCOPE);
1277        String[] attList = { "ds-privilege-name" };
1278        sc.setReturningAttributes(attList);
1279        NamingEnumeration<SearchResult> ne = dirContext.search(dnCentralAdmin, "(objectclass=*)", sc);
1280        try
1281        {
1282          while (ne.hasMore())
1283          {
1284            currentPrivileges = ne.next().getAttributes().get("ds-privilege-name").getAll();
1285          }
1286        }
1287        finally
1288        {
1289          handleCloseNamingEnumeration(ne);
1290        }
1291      }
1292
1293      // Replace properties, if needed.
1294      if (adminProperties.size() > 1)
1295      {
1296        BasicAttributes attrs =
1297            makeAttrsFromAdministratorProperties(adminProperties, updatePassword, currentPrivileges);
1298        dirContext.modifyAttributes(dnCentralAdmin, DirContext.REPLACE_ATTRIBUTE, attrs);
1299      }
1300    }
1301    catch (NameNotFoundException x)
1302    {
1303      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
1304    }
1305    catch (NoPermissionException x)
1306    {
1307      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1308    }
1309    catch (NamingException x)
1310    {
1311      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1312    }
1313    finally
1314    {
1315      handleCloseNamingEnumeration(currentPrivileges);
1316    }
1317  }
1318
1319  /**
1320   * Returns the DN of the suffix that contains the administration data.
1321   *
1322   * @return the DN of the suffix that contains the administration data.
1323   */
1324  public static String getAdministrationSuffixDN()
1325  {
1326    return "cn=admin data";
1327  }
1328
1329  /**
1330   * This method returns the DN of the entry that corresponds to the given host
1331   * name and installation path.
1332   *
1333   * @param hostname
1334   *          the host name.
1335   * @param ipath
1336   *          the installation path.
1337   * @return the DN of the entry that corresponds to the given host name and
1338   *         installation path.
1339   * @throws ADSContextException
1340   *           if something goes wrong.
1341   */
1342  private static LdapName makeDNFromHostnameAndPath(String hostname, String ipath) throws ADSContextException
1343  {
1344    return nameFromDN("cn=" + Rdn.escapeValue(hostname + "@" + ipath) + "," + getServerContainerDN());
1345  }
1346
1347  /**
1348   * This method returns the DN of the entry that corresponds to the given host
1349   * name port representation.
1350   *
1351   * @param serverUniqueId
1352   *          the host name and port.
1353   * @return the DN of the entry that corresponds to the given host name and
1354   *         port.
1355   * @throws ADSContextException
1356   *           if something goes wrong.
1357   */
1358  private static LdapName makeDNFromServerUniqueId(String serverUniqueId) throws ADSContextException
1359  {
1360    return nameFromDN("cn=" + Rdn.escapeValue(serverUniqueId) + "," + getServerContainerDN());
1361  }
1362
1363  /**
1364   * This method returns the DN of the entry that corresponds to the given
1365   * server group properties.
1366   *
1367   * @param serverGroupProperties
1368   *          the server group properties
1369   * @return the DN of the entry that corresponds to the given server group
1370   *         properties.
1371   * @throws ADSContextException
1372   *           if something goes wrong.
1373   */
1374  private static LdapName makeDNFromServerGroupProperties(Map<ServerGroupProperty, Object> serverGroupProperties)
1375      throws ADSContextException
1376  {
1377    String serverGroupId = (String) serverGroupProperties.get(ServerGroupProperty.UID);
1378    if (serverGroupId == null)
1379    {
1380      throw new ADSContextException(ErrorType.MISSING_NAME);
1381    }
1382    return nameFromDN("cn=" + Rdn.escapeValue(serverGroupId) + "," + getServerGroupContainerDN());
1383  }
1384
1385  /**
1386   * This method returns the DN of the entry that corresponds to the given
1387   * server properties.
1388   *
1389   * @param serverProperties
1390   *          the server properties.
1391   * @return the DN of the entry that corresponds to the given server
1392   *         properties.
1393   * @throws ADSContextException
1394   *           if something goes wrong.
1395   */
1396  private static LdapName makeDNFromServerProperties(Map<ServerProperty, Object> serverProperties)
1397      throws ADSContextException
1398  {
1399    String serverID = getServerID(serverProperties);
1400    if (serverID != null)
1401    {
1402      return makeDNFromServerUniqueId(serverID);
1403    }
1404
1405    String hostname = getHostname(serverProperties);
1406    try
1407    {
1408      String ipath = getInstallPath(serverProperties);
1409      return makeDNFromHostnameAndPath(hostname, ipath);
1410    }
1411    catch (ADSContextException ace)
1412    {
1413      ServerDescriptor s = ServerDescriptor.createStandalone(serverProperties);
1414      return makeDNFromServerUniqueId(s.getHostPort(true));
1415    }
1416  }
1417
1418  /**
1419   * This method returns the DN of the entry that corresponds to the given
1420   * server properties.
1421   *
1422   * @param serverProperties
1423   *          the server properties.
1424   * @return the DN of the entry that corresponds to the given server
1425   *         properties.
1426   * @throws ADSContextException
1427   *           if something goes wrong.
1428   */
1429  public static String getServerIdFromServerProperties(Map<ServerProperty, Object> serverProperties)
1430      throws ADSContextException
1431  {
1432    LdapName ldapName = makeDNFromServerProperties(serverProperties);
1433    String rdn = ldapName.get(ldapName.size() - 1);
1434    int pos = rdn.indexOf("=");
1435    return rdn.substring(pos + 1);
1436  }
1437
1438  /**
1439   * This method returns the DN of the entry that corresponds to the given
1440   * administrator properties.
1441   *
1442   * @param adminProperties
1443   *          the administrator properties.
1444   * @return the DN of the entry that corresponds to the given administrator
1445   *         properties.
1446   * @throws ADSContextException
1447   *           if something goes wrong.
1448   */
1449  private static LdapName makeDNFromAdministratorProperties(Map<AdministratorProperty, Object> adminProperties)
1450      throws ADSContextException
1451  {
1452    return makeDNFromAdministratorProperties(getAdministratorUID(adminProperties));
1453  }
1454
1455  /**
1456   * This method returns the DN of the entry that corresponds to the given
1457   * administrator properties.
1458   *
1459   * @param adminUid
1460   *          the administrator uid.
1461   * @return the DN of the entry that corresponds to the given administrator
1462   *         properties.
1463   * @throws ADSContextException
1464   *           if something goes wrong.
1465   */
1466  private static LdapName makeDNFromAdministratorProperties(String adminUid) throws ADSContextException
1467  {
1468    return nameFromDN(getAdministratorDN(adminUid));
1469  }
1470
1471  /**
1472   * Returns the attributes for some administrator properties.
1473   *
1474   * @param adminProperties
1475   *          the administrator properties.
1476   * @param passwordRequired
1477   *          Indicates if the properties should include the password.
1478   * @param currentPrivileges
1479   *          The current privilege list or null.
1480   * @return the attributes for the given administrator properties.
1481   * @throws ADSContextException
1482   *           if something goes wrong.
1483   */
1484  private static BasicAttributes makeAttrsFromAdministratorProperties(
1485      Map<AdministratorProperty, Object> adminProperties, boolean passwordRequired,
1486      NamingEnumeration<?> currentPrivileges) throws ADSContextException
1487  {
1488    BasicAttributes attrs = new BasicAttributes();
1489    Attribute oc = new BasicAttribute("objectclass");
1490    if (passwordRequired)
1491    {
1492      attrs.put("userPassword", getAdministratorPassword(adminProperties));
1493    }
1494    oc.add("top");
1495    oc.add("person");
1496    attrs.put(oc);
1497    attrs.put("sn", GLOBAL_ADMIN_UID);
1498    if (adminProperties.containsKey(AdministratorProperty.DESCRIPTION))
1499    {
1500      attrs.put("description", adminProperties.get(AdministratorProperty.DESCRIPTION));
1501    }
1502    Attribute privilegeAtt;
1503    if (adminProperties.containsKey(AdministratorProperty.PRIVILEGE))
1504    {
1505      // We assume that privilege strings provided in
1506      // AdministratorProperty.PRIVILEGE
1507      // are valid privileges represented as a LinkedList of string.
1508      privilegeAtt = new BasicAttribute("ds-privilege-name");
1509      if (currentPrivileges != null)
1510      {
1511        while (currentPrivileges.hasMoreElements())
1512        {
1513          privilegeAtt.add(currentPrivileges.nextElement().toString());
1514        }
1515      }
1516
1517      LinkedList<?> privileges = (LinkedList<?>) adminProperties.get(AdministratorProperty.PRIVILEGE);
1518      for (Object o : privileges)
1519      {
1520        String p = o.toString();
1521        if (p.startsWith("-"))
1522        {
1523          privilegeAtt.remove(p.substring(1));
1524        }
1525        else
1526        {
1527          privilegeAtt.add(p);
1528        }
1529      }
1530    }
1531    else
1532    {
1533      privilegeAtt = addRootPrivileges();
1534    }
1535    attrs.put(privilegeAtt);
1536
1537    // Add the RootDNs Password policy so the password do not expire.
1538    attrs.put("ds-pwp-password-policy-dn", "cn=Root Password Policy,cn=Password Policies,cn=config");
1539
1540    return attrs;
1541  }
1542
1543  /**
1544   * Builds an attribute which contains 'root' privileges.
1545   *
1546   * @return The attribute which contains 'root' privileges.
1547   */
1548  private static Attribute addRootPrivileges()
1549  {
1550    Attribute privilege = new BasicAttribute("ds-privilege-name");
1551    privilege.add("bypass-acl");
1552    privilege.add("modify-acl");
1553    privilege.add("config-read");
1554    privilege.add("config-write");
1555    privilege.add("ldif-import");
1556    privilege.add("ldif-export");
1557    privilege.add("backend-backup");
1558    privilege.add("backend-restore");
1559    privilege.add("server-shutdown");
1560    privilege.add("server-restart");
1561    privilege.add("disconnect-client");
1562    privilege.add("cancel-request");
1563    privilege.add("password-reset");
1564    privilege.add("update-schema");
1565    privilege.add("privilege-change");
1566    privilege.add("unindexed-search");
1567    privilege.add("subentry-write");
1568    privilege.add("changelog-read");
1569    return privilege;
1570  }
1571
1572  /**
1573   * Returns the attributes for some server properties.
1574   *
1575   * @param serverProperties
1576   *          the server properties.
1577   * @param addObjectClass
1578   *          Indicates if the object class has to be added.
1579   * @return the attributes for the given server properties.
1580   */
1581  private static BasicAttributes makeAttrsFromServerProperties(Map<ServerProperty, Object> serverProperties,
1582      boolean addObjectClass)
1583  {
1584    BasicAttributes result = new BasicAttributes();
1585
1586    // Transform 'properties' into 'attributes'
1587    for (ServerProperty prop : serverProperties.keySet())
1588    {
1589      Attribute attr = makeAttrFromServerProperty(prop, serverProperties.get(prop));
1590      if (attr != null)
1591      {
1592        result.put(attr);
1593      }
1594    }
1595    if (addObjectClass)
1596    {
1597      // Add the objectclass attribute value
1598      // TODO: use another structural objectclass
1599      Attribute oc = new BasicAttribute("objectclass");
1600      oc.add("top");
1601      oc.add("ds-cfg-branch");
1602      oc.add("extensibleobject");
1603      result.put(oc);
1604    }
1605    return result;
1606  }
1607
1608  /**
1609   * Returns the attribute for a given server property.
1610   *
1611   * @param property
1612   *          the server property.
1613   * @param value
1614   *          the value.
1615   * @return the attribute for a given server property.
1616   */
1617  private static Attribute makeAttrFromServerProperty(ServerProperty property, Object value)
1618  {
1619    Attribute result;
1620
1621    switch (property)
1622    {
1623    case INSTANCE_PUBLIC_KEY_CERTIFICATE:
1624      result = null; // used in separate instance key entry
1625      break;
1626    case GROUPS:
1627      result = new BasicAttribute(ServerProperty.GROUPS.getAttributeName());
1628      for (Object o : ((Set<?>) value))
1629      {
1630        result.add(o);
1631      }
1632      break;
1633    default:
1634      result = new BasicAttribute(property.getAttributeName(), value);
1635    }
1636    return result;
1637  }
1638
1639  /**
1640   * Returns the attributes for some server group properties.
1641   *
1642   * @param serverGroupProperties
1643   *          the server group properties.
1644   * @return the attributes for the given server group properties.
1645   */
1646  private static BasicAttributes makeAttrsFromServerGroupProperties(
1647      Map<ServerGroupProperty, Object> serverGroupProperties)
1648  {
1649    BasicAttributes result = new BasicAttributes();
1650
1651    // Transform 'properties' into 'attributes'
1652    for (ServerGroupProperty prop : serverGroupProperties.keySet())
1653    {
1654      Attribute attr = makeAttrFromServerGroupProperty(prop, serverGroupProperties.get(prop));
1655      if (attr != null)
1656      {
1657        result.put(attr);
1658      }
1659    }
1660    return result;
1661  }
1662
1663  /**
1664   * Returns the attributes for some server group properties.
1665   *
1666   * @param serverGroupProperties
1667   *          the server group properties.
1668   * @return the attributes for the given server group properties.
1669   */
1670  private static BasicAttributes makeAttrsFromServerGroupProperties(Set<ServerGroupProperty> serverGroupProperties)
1671  {
1672    BasicAttributes result = new BasicAttributes();
1673
1674    // Transform 'properties' into 'attributes'
1675    for (ServerGroupProperty prop : serverGroupProperties)
1676    {
1677      Attribute attr = makeAttrFromServerGroupProperty(prop, null);
1678      if (attr != null)
1679      {
1680        result.put(attr);
1681      }
1682    }
1683    return result;
1684  }
1685
1686  /**
1687   * Returns the attribute for a given server group property.
1688   *
1689   * @param property
1690   *          the server group property.
1691   * @param value
1692   *          the value.
1693   * @return the attribute for a given server group property.
1694   */
1695  private static Attribute makeAttrFromServerGroupProperty(ServerGroupProperty property, Object value)
1696  {
1697    switch (property)
1698    {
1699    case MEMBERS:
1700      Attribute result = new BasicAttribute(ServerGroupProperty.MEMBERS.getAttributeName());
1701      for (Object o : ((Set<?>) value))
1702      {
1703        result.add(o);
1704      }
1705      return result;
1706    default:
1707      return new BasicAttribute(property.getAttributeName(), value);
1708    }
1709  }
1710
1711  /**
1712   * Returns the properties of a server group for some LDAP attributes.
1713   *
1714   * @param attrs
1715   *          the LDAP attributes.
1716   * @return the properties of a server group for some LDAP attributes.
1717   * @throws ADSContextException
1718   *           if something goes wrong.
1719   */
1720  private Map<ServerGroupProperty, Object> makePropertiesFromServerGroupAttrs(Attributes attrs)
1721      throws ADSContextException
1722  {
1723    Map<ServerGroupProperty, Object> result = new HashMap<>();
1724    try
1725    {
1726      for (ServerGroupProperty prop : ServerGroupProperty.values())
1727      {
1728        Attribute attr = attrs.get(prop.getAttributeName());
1729        if (attr == null)
1730        {
1731          continue;
1732        }
1733        Object value;
1734
1735        if (attr.size() >= 1 && MULTIVALUED_SERVER_GROUP_PROPERTIES.contains(prop))
1736        {
1737          Set<String> set = new HashSet<>();
1738          NamingEnumeration<?> ae = attr.getAll();
1739          try
1740          {
1741            while (ae.hasMore())
1742            {
1743              set.add((String) ae.next());
1744            }
1745          }
1746          finally
1747          {
1748            ae.close();
1749          }
1750          value = set;
1751        }
1752        else
1753        {
1754          value = attr.get(0);
1755        }
1756
1757        result.put(prop, value);
1758      }
1759    }
1760    catch (NamingException x)
1761    {
1762      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1763    }
1764    return result;
1765  }
1766
1767  /**
1768   * Returns the properties of a server for some LDAP attributes.
1769   *
1770   * @param attrs
1771   *          the LDAP attributes.
1772   * @return the properties of a server for some LDAP attributes.
1773   * @throws ADSContextException
1774   *           if something goes wrong.
1775   */
1776  private Map<ServerProperty, Object> makePropertiesFromServerAttrs(Attributes attrs) throws ADSContextException
1777  {
1778    Map<ServerProperty, Object> result = new HashMap<>();
1779    try
1780    {
1781      NamingEnumeration<? extends Attribute> ne = attrs.getAll();
1782      while (ne.hasMore())
1783      {
1784        Attribute attr = ne.next();
1785        String attrID = attr.getID();
1786        Object value;
1787
1788        if (attrID.endsWith(";binary"))
1789        {
1790          attrID = attrID.substring(0, attrID.lastIndexOf(";binary"));
1791        }
1792
1793        ServerProperty prop = null;
1794        ServerProperty[] props = ServerProperty.values();
1795        for (int i = 0; i < props.length && prop == null; i++)
1796        {
1797          String v = props[i].getAttributeName();
1798          if (attrID.equalsIgnoreCase(v))
1799          {
1800            prop = props[i];
1801          }
1802        }
1803        if (prop == null)
1804        {
1805          // Do not handle it
1806        }
1807        else
1808        {
1809          if (attr.size() >= 1 && MULTIVALUED_SERVER_PROPERTIES.contains(prop))
1810          {
1811            Set<String> set = new HashSet<>();
1812            NamingEnumeration<?> ae = attr.getAll();
1813            try
1814            {
1815              while (ae.hasMore())
1816              {
1817                set.add((String) ae.next());
1818              }
1819            }
1820            finally
1821            {
1822              ae.close();
1823            }
1824            value = set;
1825          }
1826          else
1827          {
1828            value = attr.get(0);
1829          }
1830
1831          result.put(prop, value);
1832        }
1833      }
1834    }
1835    catch (NamingException x)
1836    {
1837      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1838    }
1839    return result;
1840  }
1841
1842  /**
1843   * Returns the properties of an administrator for some rdn and LDAP
1844   * attributes.
1845   *
1846   * @param rdn
1847   *          the RDN.
1848   * @param attrs
1849   *          the LDAP attributes.
1850   * @return the properties of an administrator for the given rdn and LDAP
1851   *         attributes.
1852   * @throws ADSContextException
1853   *           if something goes wrong.
1854   */
1855  private Map<AdministratorProperty, Object> makePropertiesFromAdministratorAttrs(String rdn, Attributes attrs)
1856      throws ADSContextException
1857  {
1858    Map<AdministratorProperty, Object> result = new HashMap<>();
1859    LdapName nameObj;
1860    nameObj = nameFromDN(rdn);
1861    String dn = nameObj + "," + getAdministratorContainerDN();
1862    result.put(AdministratorProperty.ADMINISTRATOR_DN, dn);
1863    NamingEnumeration<? extends Attribute> ne = null;
1864    try
1865    {
1866      ne = attrs.getAll();
1867      while (ne.hasMore())
1868      {
1869        Attribute attr = ne.next();
1870        String attrID = attr.getID();
1871        Object value;
1872
1873        if ("cn".equalsIgnoreCase(attrID))
1874        {
1875          value = attr.get(0);
1876          result.put(AdministratorProperty.UID, value);
1877        }
1878        else if ("userpassword".equalsIgnoreCase(attrID))
1879        {
1880          value = new String((byte[]) attr.get());
1881          result.put(AdministratorProperty.PASSWORD, value);
1882        }
1883        else if ("description".equalsIgnoreCase(attrID))
1884        {
1885          value = attr.get(0);
1886          result.put(AdministratorProperty.DESCRIPTION, value);
1887        }
1888        else if ("ds-privilege-name".equalsIgnoreCase(attrID))
1889        {
1890          LinkedHashSet<String> privileges = new LinkedHashSet<>();
1891          NamingEnumeration<?> attValueList = attr.getAll();
1892          while (attValueList.hasMoreElements())
1893          {
1894            privileges.add(attValueList.next().toString());
1895          }
1896          result.put(AdministratorProperty.PRIVILEGE, privileges);
1897        }
1898      }
1899    }
1900    catch (NamingException x)
1901    {
1902      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1903    }
1904    finally
1905    {
1906      handleCloseNamingEnumeration(ne);
1907    }
1908
1909    return result;
1910  }
1911
1912  /**
1913   * Returns the parent entry of the server entries.
1914   *
1915   * @return the parent entry of the server entries.
1916   */
1917  public static String getServerContainerDN()
1918  {
1919    return "cn=Servers," + getAdministrationSuffixDN();
1920  }
1921
1922  /**
1923   * Returns the parent entry of the administrator entries.
1924   *
1925   * @return the parent entry of the administrator entries.
1926   */
1927  public static String getAdministratorContainerDN()
1928  {
1929    return "cn=Administrators," + getAdministrationSuffixDN();
1930  }
1931
1932  /**
1933   * Returns the parent entry of the server group entries.
1934   *
1935   * @return the parent entry of the server group entries.
1936   */
1937  public static String getServerGroupContainerDN()
1938  {
1939    return "cn=Server Groups," + getAdministrationSuffixDN();
1940  }
1941
1942  /**
1943   * Returns the all server group entry DN.
1944   *
1945   * @return the all server group entry DN.
1946   */
1947  private static String getAllServerGroupDN()
1948  {
1949    return "cn=" + Rdn.escapeValue(ALL_SERVERGROUP_NAME) + "," + getServerGroupContainerDN();
1950  }
1951
1952  /**
1953   * Returns the host name for the given properties.
1954   *
1955   * @param serverProperties
1956   *          the server properties.
1957   * @return the host name for the given properties.
1958   * @throws ADSContextException
1959   *           if the host name could not be found or its value is not valid.
1960   */
1961  private static String getHostname(Map<ServerProperty, Object> serverProperties) throws ADSContextException
1962  {
1963    String result = (String) serverProperties.get(ServerProperty.HOST_NAME);
1964    if (result == null)
1965    {
1966      throw new ADSContextException(ErrorType.MISSING_HOSTNAME);
1967    }
1968    else if (result.length() == 0)
1969    {
1970      throw new ADSContextException(ErrorType.NOVALID_HOSTNAME);
1971    }
1972    return result;
1973  }
1974
1975  /**
1976   * Returns the Server ID for the given properties.
1977   *
1978   * @param serverProperties
1979   *          the server properties.
1980   * @return the server ID for the given properties or null.
1981   */
1982  private static String getServerID(Map<ServerProperty, Object> serverProperties)
1983  {
1984    String result = (String) serverProperties.get(ServerProperty.ID);
1985    if (result != null && result.length() == 0)
1986    {
1987      result = null;
1988    }
1989    return result;
1990  }
1991
1992  /**
1993   * Returns the install path for the given properties.
1994   *
1995   * @param serverProperties
1996   *          the server properties.
1997   * @return the install path for the given properties.
1998   * @throws ADSContextException
1999   *           if the install path could not be found or its value is not valid.
2000   */
2001  private static String getInstallPath(Map<ServerProperty, Object> serverProperties) throws ADSContextException
2002  {
2003    String result = (String) serverProperties.get(ServerProperty.INSTANCE_PATH);
2004    if (result == null)
2005    {
2006      throw new ADSContextException(ErrorType.MISSING_IPATH);
2007    }
2008    else if (result.length() == 0)
2009    {
2010      throw new ADSContextException(ErrorType.NOVALID_IPATH);
2011    }
2012    return result;
2013  }
2014
2015  /**
2016   * Returns the Administrator UID for the given properties.
2017   *
2018   * @param adminProperties
2019   *          the server properties.
2020   * @return the Administrator UID for the given properties.
2021   * @throws ADSContextException
2022   *           if the administrator UID could not be found.
2023   */
2024  private static String getAdministratorUID(Map<AdministratorProperty, Object> adminProperties)
2025      throws ADSContextException
2026  {
2027    String result = (String) adminProperties.get(AdministratorProperty.UID);
2028    if (result == null)
2029    {
2030      throw new ADSContextException(ErrorType.MISSING_ADMIN_UID);
2031    }
2032    return result;
2033  }
2034
2035  /**
2036   * Returns the Administrator password for the given properties.
2037   *
2038   * @param adminProperties
2039   *          the server properties.
2040   * @return the Administrator password for the given properties.
2041   * @throws ADSContextException
2042   *           if the administrator password could not be found.
2043   */
2044  private static String getAdministratorPassword(Map<AdministratorProperty, Object> adminProperties)
2045      throws ADSContextException
2046  {
2047    String result = (String) adminProperties.get(AdministratorProperty.PASSWORD);
2048    if (result == null)
2049    {
2050      throw new ADSContextException(ErrorType.MISSING_ADMIN_PASSWORD);
2051    }
2052    return result;
2053  }
2054
2055  // LDAP utilities
2056  /**
2057   * Returns the LdapName object for the given dn.
2058   *
2059   * @param dn
2060   *          the DN.
2061   * @return the LdapName object for the given dn.
2062   * @throws ADSContextException
2063   *           if a valid LdapName could not be retrieved for the given dn.
2064   */
2065  private static LdapName nameFromDN(String dn) throws ADSContextException
2066  {
2067    try
2068    {
2069      return new LdapName(dn);
2070    }
2071    catch (InvalidNameException x)
2072    {
2073      logger.error(LocalizableMessage.raw("Error parsing dn " + dn, x));
2074      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2075    }
2076  }
2077
2078  /**
2079   * Returns the String rdn for the given search result name.
2080   *
2081   * @param rdnName
2082   *          the search result name.
2083   * @return the String rdn for the given search result name.
2084   * @throws ADSContextException
2085   *           if a valid String rdn could not be retrieved for the given result
2086   *           name.
2087   */
2088  private static String getRdn(String rdnName) throws ADSContextException
2089  {
2090    // Transform the JNDI name into a RDN string
2091    try
2092    {
2093      return new CompositeName(rdnName).get(0);
2094    }
2095    catch (InvalidNameException x)
2096    {
2097      logger.error(LocalizableMessage.raw("Error parsing rdn " + rdnName, x));
2098      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2099    }
2100  }
2101
2102  /**
2103   * Tells whether an entry with the provided DN exists.
2104   *
2105   * @param dn
2106   *          the DN to check.
2107   * @return <CODE>true</CODE> if the entry exists and <CODE>false</CODE> if it
2108   *         does not.
2109   * @throws ADSContextException
2110   *           if an error occurred while checking if the entry exists or not.
2111   */
2112  private boolean isExistingEntry(LdapName dn) throws ADSContextException
2113  {
2114    try
2115    {
2116      SearchControls sc = new SearchControls();
2117
2118      sc.setSearchScope(SearchControls.OBJECT_SCOPE);
2119      sc.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES });
2120      NamingEnumeration<SearchResult> sr = getDirContext().search(dn, "(objectclass=*)", sc);
2121      boolean result = false;
2122      try
2123      {
2124        while (sr.hasMore())
2125        {
2126          sr.next();
2127          result = true;
2128        }
2129      }
2130      finally
2131      {
2132        sr.close();
2133      }
2134      return result;
2135    }
2136    catch (NameNotFoundException x)
2137    {
2138      return false;
2139    }
2140    catch (NoPermissionException x)
2141    {
2142      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
2143    }
2144    catch (javax.naming.NamingException x)
2145    {
2146      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2147    }
2148  }
2149
2150  /**
2151   * Creates a container entry with the given dn.
2152   *
2153   * @param dn
2154   *          the entry of the new entry to be created.
2155   * @throws ADSContextException
2156   *           if the entry could not be created.
2157   */
2158  private void createContainerEntry(String dn) throws ADSContextException
2159  {
2160    BasicAttributes attrs = new BasicAttributes();
2161    Attribute oc = new BasicAttribute("objectclass");
2162    oc.add("top");
2163    oc.add("ds-cfg-branch");
2164    attrs.put(oc);
2165    createEntry(dn, attrs);
2166  }
2167
2168  /**
2169   * Creates the administrator container entry.
2170   *
2171   * @throws ADSContextException
2172   *           if the entry could not be created.
2173   */
2174  private void createAdministratorContainerEntry() throws ADSContextException
2175  {
2176    BasicAttributes attrs = new BasicAttributes();
2177    Attribute oc = new BasicAttribute("objectclass");
2178    oc.add("groupofurls");
2179    attrs.put(oc);
2180    attrs.put("memberURL", "ldap:///" + getAdministratorContainerDN() + "??one?(objectclass=*)");
2181    attrs.put("description", "Group of identities which have full access.");
2182    createEntry(getAdministratorContainerDN(), attrs);
2183  }
2184
2185  /**
2186   * Creates the top container entry.
2187   *
2188   * @throws ADSContextException
2189   *           if the entry could not be created.
2190   */
2191  private void createTopContainerEntry() throws ADSContextException
2192  {
2193    BasicAttributes attrs = new BasicAttributes();
2194    Attribute oc = new BasicAttribute("objectclass");
2195    oc.add("top");
2196    oc.add("ds-cfg-branch");
2197    attrs.put(oc);
2198    createEntry(getAdministrationSuffixDN(), attrs);
2199  }
2200
2201  /**
2202   * Creates an entry with the provided dn and attributes.
2203   *
2204   * @param dn
2205   *          the dn of the entry.
2206   * @param attrs
2207   *          the attributes of the entry.
2208   * @throws ADSContextException
2209   *           if the entry could not be created.
2210   */
2211  private void createEntry(String dn, Attributes attrs) throws ADSContextException
2212  {
2213    try
2214    {
2215      DirContext ctx = getDirContext().createSubcontext(nameFromDN(dn), attrs);
2216      ctx.close();
2217    }
2218    catch (NamingException x)
2219    {
2220      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2221    }
2222  }
2223
2224  /**
2225   * Creates the Administration Suffix.
2226   *
2227   * @param backendName
2228   *          the backend name to be used for the Administration Suffix. If this
2229   *          value is null the default backendName for the Administration
2230   *          Suffix will be used.
2231   * @throws ADSContextException
2232   *           if something goes wrong.
2233   */
2234  public void createAdministrationSuffix(String backendName) throws ADSContextException
2235  {
2236    ADSContextHelper helper = new ADSContextHelper();
2237    String ben = backendName;
2238    if (backendName == null)
2239    {
2240      ben = getDefaultBackendName();
2241    }
2242    helper.createAdministrationSuffix(getDirContext(), ben);
2243  }
2244
2245  /**
2246   * Returns the default backend name of the administration data.
2247   *
2248   * @return the default backend name of the administration data.
2249   */
2250  public static String getDefaultBackendName()
2251  {
2252    return "adminRoot";
2253  }
2254
2255  /**
2256   * Returns the LDIF file of the administration data.
2257   *
2258   * @return the LDIF file of the administration data.
2259   */
2260  public static String getAdminLDIFFile()
2261  {
2262    return "config" + File.separator + "admin-backend.ldif";
2263  }
2264
2265  /** CryptoManager related types, fields, and methods. */
2266
2267  /**
2268   * Returns the parent entry of the server key entries in ADS.
2269   *
2270   * @return the parent entry of the server key entries in ADS.
2271   */
2272  public static String getInstanceKeysContainerDN()
2273  {
2274    return "cn=instance keys," + getAdministrationSuffixDN();
2275  }
2276
2277  /**
2278   * Returns the parent entry of the secret key entries in ADS.
2279   *
2280   * @return the parent entry of the secret key entries in ADS.
2281   */
2282  public static String getSecretKeysContainerDN()
2283  {
2284    return "cn=secret keys," + getAdministrationSuffixDN();
2285  }
2286
2287  /**
2288   * Tells whether the provided server is registered in the registry.
2289   *
2290   * @param server
2291   *          the server.
2292   * @param registry
2293   *          the registry.
2294   * @return <CODE>true</CODE> if the server is registered in the registry and
2295   *         <CODE>false</CODE> otherwise.
2296   */
2297  public static boolean isRegistered(ServerDescriptor server, Set<Map<ADSContext.ServerProperty, Object>> registry)
2298  {
2299    for (Map<ADSContext.ServerProperty, Object> s : registry)
2300    {
2301      ServerDescriptor servInRegistry = ServerDescriptor.createStandalone(s);
2302      if (servInRegistry.getId().equals(server.getId()))
2303      {
2304        return true;
2305      }
2306    }
2307    return false;
2308  }
2309
2310  /**
2311   * Register instance key-pair public-key certificate provided in
2312   * serverProperties: generate a key-id attribute if one is not provided (as
2313   * expected); add an instance key public-key certificate entry for the key
2314   * certificate; and associate the certificate entry with the server entry via
2315   * the key ID attribute.
2316   *
2317   * @param serverProperties
2318   *          Properties of the server being registered to which the instance
2319   *          key entry belongs.
2320   * @param serverEntryDn
2321   *          The server's ADS entry DN.
2322   * @throws NamingException
2323   *           In case some JNDI operation fails.
2324   * @throws CryptoManager.CryptoManagerException
2325   *           In case there is a problem getting the instance public key
2326   *           certificate ID.
2327   */
2328  private void registerInstanceKeyCertificate(Map<ServerProperty, Object> serverProperties, LdapName serverEntryDn)
2329      throws ADSContextException
2330  {
2331    ADSContextHelper helper = new ADSContextHelper();
2332    helper.registerInstanceKeyCertificate(dirContext, serverProperties, serverEntryDn);
2333  }
2334
2335  /**
2336   * Return the set of valid (i.e., not tagged as compromised) instance key-pair
2337   * public-key certificate entries in ADS. NOTE: calling this method assumes
2338   * that all the jar files are present in the classpath.
2339   *
2340   * @return The set of valid (i.e., not tagged as compromised) instance
2341   *         key-pair public-key certificate entries in ADS represented as a Map
2342   *         from ds-cfg-key-id value to ds-cfg-public-key-certificate;binary
2343   *         value. Note that the collection might be empty.
2344   * @throws ADSContextException
2345   *           in case of problems with the entry search.
2346   * @see org.opends.server.crypto.CryptoManagerImpl#getTrustedCertificates
2347   */
2348  public Map<String, byte[]> getTrustedCertificates() throws ADSContextException
2349  {
2350    final Map<String, byte[]> certificateMap = new HashMap<>();
2351    final String baseDNStr = getInstanceKeysContainerDN();
2352    try
2353    {
2354      ADSContextHelper helper = new ADSContextHelper();
2355      final LdapName baseDN = new LdapName(baseDNStr);
2356      final String FILTER_OC_INSTANCE_KEY = "(objectclass=" + helper.getOcCryptoInstanceKey() + ")";
2357      final String FILTER_NOT_COMPROMISED = "(!(" + helper.getAttrCryptoKeyCompromisedTime() + "=*))";
2358      final String searchFilter = "(&" + FILTER_OC_INSTANCE_KEY + FILTER_NOT_COMPROMISED + ")";
2359      final SearchControls searchControls = new SearchControls();
2360      searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
2361      final String attrIDs[] =
2362          { ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName(),
2363            ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE.getAttributeName() + ";binary" };
2364      searchControls.setReturningAttributes(attrIDs);
2365      NamingEnumeration<SearchResult> keyEntries = dirContext.search(baseDN, searchFilter, searchControls);
2366      try
2367      {
2368        while (keyEntries.hasMore())
2369        {
2370          final SearchResult entry = keyEntries.next();
2371          final Attributes attrs = entry.getAttributes();
2372          final Attribute keyIDAttr = attrs.get(attrIDs[0]);
2373          final Attribute keyCertAttr = attrs.get(attrIDs[1]);
2374          if (null == keyIDAttr || null == keyCertAttr)
2375          {
2376            continue;// schema viol.
2377          }
2378          certificateMap.put((String) keyIDAttr.get(), (byte[]) keyCertAttr.get());
2379        }
2380      }
2381      finally
2382      {
2383        try
2384        {
2385          keyEntries.close();
2386        }
2387        catch (Exception ex)
2388        {
2389          logger.warn(LocalizableMessage.raw("Unexpected error closing enumeration on ADS key pairs", ex));
2390        }
2391      }
2392    }
2393    catch (NamingException x)
2394    {
2395      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2396    }
2397    return certificateMap;
2398  }
2399
2400  /**
2401   * Merge the contents of this ADSContext with the contents of the provided
2402   * ADSContext. Note that only the contents of this ADSContext will be updated.
2403   *
2404   * @param adsCtx
2405   *          the other ADSContext to merge the contents with.
2406   * @throws ADSContextException
2407   *           if there was an error during the merge.
2408   */
2409  public void mergeWithRegistry(ADSContext adsCtx) throws ADSContextException
2410  {
2411    try
2412    {
2413      mergeAdministrators(adsCtx);
2414      mergeServerGroups(adsCtx);
2415      mergeServers(adsCtx);
2416    }
2417    catch (ADSContextException adce)
2418    {
2419      LocalizableMessage msg = ERR_ADS_MERGE.get(ConnectionUtils.getHostPort(getDirContext()),
2420          ConnectionUtils.getHostPort(adsCtx.getDirContext()), adce.getMessageObject());
2421      throw new ADSContextException(ErrorType.ERROR_MERGING, msg, adce);
2422    }
2423  }
2424
2425  /**
2426   * Merge the administrator contents of this ADSContext with the contents of
2427   * the provided ADSContext. Note that only the contents of this ADSContext
2428   * will be updated.
2429   *
2430   * @param adsCtx
2431   *          the other ADSContext to merge the contents with.
2432   * @throws ADSContextException
2433   *           if there was an error during the merge.
2434   */
2435  private void mergeAdministrators(ADSContext adsCtx) throws ADSContextException
2436  {
2437    Set<Map<AdministratorProperty, Object>> admins2 = adsCtx.readAdministratorRegistry();
2438    SortedSet<String> notDefinedAdmins = new TreeSet<>();
2439    for (Map<AdministratorProperty, Object> admin2 : admins2)
2440    {
2441      String uid = (String) admin2.get(AdministratorProperty.UID);
2442      if (!isAdministratorAlreadyRegistered(uid))
2443      {
2444        notDefinedAdmins.add(uid);
2445      }
2446    }
2447    if (!notDefinedAdmins.isEmpty())
2448    {
2449      LocalizableMessage msg = ERR_ADS_ADMINISTRATOR_MERGE.get(
2450          ConnectionUtils.getHostPort(adsCtx.getDirContext()), ConnectionUtils.getHostPort(getDirContext()),
2451          joinAsString(Constants.LINE_SEPARATOR, notDefinedAdmins), ConnectionUtils.getHostPort(getDirContext()));
2452      throw new ADSContextException(ErrorType.ERROR_MERGING, msg, null);
2453    }
2454  }
2455
2456  /**
2457   * Merge the groups contents of this ADSContext with the contents of the
2458   * provided ADSContext. Note that only the contents of this ADSContext will be
2459   * updated.
2460   *
2461   * @param adsCtx
2462   *          the other ADSContext to merge the contents with.
2463   * @throws ADSContextException
2464   *           if there was an error during the merge.
2465   */
2466  private void mergeServerGroups(ADSContext adsCtx) throws ADSContextException
2467  {
2468    Set<Map<ServerGroupProperty, Object>> serverGroups1 = readServerGroupRegistry();
2469    Set<Map<ServerGroupProperty, Object>> serverGroups2 = adsCtx.readServerGroupRegistry();
2470
2471    for (Map<ServerGroupProperty, Object> group2 : serverGroups2)
2472    {
2473      Map<ServerGroupProperty, Object> group1 = null;
2474      String uid2 = (String) group2.get(ServerGroupProperty.UID);
2475      for (Map<ServerGroupProperty, Object> gr : serverGroups1)
2476      {
2477        String uid1 = (String) gr.get(ServerGroupProperty.UID);
2478        if (uid1.equalsIgnoreCase(uid2))
2479        {
2480          group1 = gr;
2481          break;
2482        }
2483      }
2484
2485      if (group1 != null)
2486      {
2487        // Merge the members, keep the description on this ADS.
2488        Set<String> member1List = getServerGroupMemberList(uid2);
2489        if (member1List == null)
2490        {
2491          member1List = new HashSet<>();
2492        }
2493        Set<String> member2List = adsCtx.getServerGroupMemberList(uid2);
2494        if (member2List != null && !member2List.isEmpty())
2495        {
2496          member1List.addAll(member2List);
2497          Map<ServerGroupProperty, Object> newProperties = new HashMap<>();
2498          newProperties.put(ServerGroupProperty.MEMBERS, member1List);
2499          updateServerGroup(uid2, newProperties);
2500        }
2501      }
2502      else
2503      {
2504        createServerGroup(group2);
2505      }
2506    }
2507  }
2508
2509  /**
2510   * Merge the server contents of this ADSContext with the contents of the
2511   * provided ADSContext. Note that only the contents of this ADSContext will be
2512   * updated.
2513   *
2514   * @param adsCtx
2515   *          the other ADSContext to merge the contents with.
2516   * @throws ADSContextException
2517   *           if there was an error during the merge.
2518   */
2519  private void mergeServers(ADSContext adsCtx) throws ADSContextException
2520  {
2521    for (Map<ServerProperty, Object> server2 : adsCtx.readServerRegistry())
2522    {
2523      if (!isServerAlreadyRegistered(server2))
2524      {
2525        registerServer(server2);
2526      }
2527    }
2528  }
2529
2530  private void handleCloseNamingEnumeration(NamingEnumeration<?> ne) throws ADSContextException
2531  {
2532    if (ne != null)
2533    {
2534      try
2535      {
2536        ne.close();
2537      }
2538      catch (NamingException ex)
2539      {
2540        throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, ex);
2541      }
2542    }
2543  }
2544}