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 OpenDJ.jar has already been loaded.
1034   *
1035   * @param backendName
1036   *          the backend name which will handle admin information.
1037   *          <CODE>null</CODE> to use the default backend name for the admin
1038   *          information.
1039   * @throws ADSContextException
1040   *           if something goes wrong.
1041   */
1042  public void createAdminData(String backendName) throws ADSContextException
1043  {
1044    // Add the administration suffix
1045    createAdministrationSuffix(backendName);
1046    createAdminDataContainers();
1047  }
1048
1049  /** Create container entries. */
1050  private void createAdminDataContainers() throws ADSContextException
1051  {
1052    // Create the DIT below the administration suffix
1053    if (!isExistingEntry(nameFromDN(getAdministrationSuffixDN())))
1054    {
1055      createTopContainerEntry();
1056    }
1057    if (!isExistingEntry(nameFromDN(getAdministratorContainerDN())))
1058    {
1059      createAdministratorContainerEntry();
1060    }
1061    if (!isExistingEntry(nameFromDN(getServerContainerDN())))
1062    {
1063      createContainerEntry(getServerContainerDN());
1064    }
1065    if (!isExistingEntry(nameFromDN(getServerGroupContainerDN())))
1066    {
1067      createContainerEntry(getServerGroupContainerDN());
1068    }
1069
1070    // Add the default "all-servers" group
1071    if (!isExistingEntry(nameFromDN(getAllServerGroupDN())))
1072    {
1073      Map<ServerGroupProperty, Object> allServersGroupsMap = new HashMap<>();
1074      allServersGroupsMap.put(ServerGroupProperty.UID, ALL_SERVERGROUP_NAME);
1075      createServerGroup(allServersGroupsMap);
1076    }
1077
1078    // Create the CryptoManager instance key DIT below the administration suffix
1079    if (!isExistingEntry(nameFromDN(getInstanceKeysContainerDN())))
1080    {
1081      createContainerEntry(getInstanceKeysContainerDN());
1082    }
1083
1084    // Create the CryptoManager secret key DIT below the administration suffix
1085    if (!isExistingEntry(nameFromDN(getSecretKeysContainerDN())))
1086    {
1087      createContainerEntry(getSecretKeysContainerDN());
1088    }
1089  }
1090
1091  /**
1092   * Removes the administration data.
1093   *
1094   * @param removeAdministrators
1095   *          {@code true} if administrators should be removed. It may not be
1096   *          possible to remove administrators if the operation is being
1097   *          performed by one of the administrators because it will cause the
1098   *          administrator to be disconnected.
1099   * @throws ADSContextException
1100   *           if something goes wrong.
1101   */
1102  public void removeAdminData(boolean removeAdministrators) throws ADSContextException
1103  {
1104    String[] dns = { getServerContainerDN(), getServerGroupContainerDN(),
1105      removeAdministrators ? getAdministratorContainerDN() : null };
1106    try
1107    {
1108      Control[] controls = new Control[] { new SubtreeDeleteControl() };
1109      LdapContext tmpContext = dirContext.newInstance(controls);
1110      try
1111      {
1112        for (String dn : dns)
1113        {
1114          if (dn != null)
1115          {
1116            LdapName ldapName = nameFromDN(dn);
1117            if (isExistingEntry(ldapName))
1118            {
1119              tmpContext.destroySubcontext(dn);
1120            }
1121          }
1122        }
1123      }
1124      finally
1125      {
1126        try
1127        {
1128          tmpContext.close();
1129        }
1130        catch (Exception ex)
1131        {
1132          logger.warn(LocalizableMessage.raw("Error while closing LDAP connection after removing admin data", ex));
1133        }
1134      }
1135      // Recreate the container entries:
1136      createAdminDataContainers();
1137    }
1138    catch (NamingException x)
1139    {
1140      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1141    }
1142  }
1143
1144  /**
1145   * Returns <CODE>true</CODE> if the server contains Administration Data and
1146   * <CODE>false</CODE> otherwise.
1147   *
1148   * @return <CODE>true</CODE> if the server contains Administration Data and
1149   *         <CODE>false</CODE> otherwise.
1150   * @throws ADSContextException
1151   *           if something goes wrong.
1152   */
1153  public boolean hasAdminData() throws ADSContextException
1154  {
1155    String[] dns = { getAdministratorContainerDN(), getAllServerGroupDN(), getServerContainerDN(),
1156      getInstanceKeysContainerDN(), getSecretKeysContainerDN() };
1157    boolean hasAdminData = true;
1158    for (int i = 0; i < dns.length && hasAdminData; i++)
1159    {
1160      hasAdminData = isExistingEntry(nameFromDN(dns[i]));
1161    }
1162    return hasAdminData;
1163  }
1164
1165  /**
1166   * Returns the DN of the administrator for a given UID.
1167   *
1168   * @param uid
1169   *          the UID to be used to generate the DN.
1170   * @return the DN of the administrator for the given UID:
1171   */
1172  public static String getAdministratorDN(String uid)
1173  {
1174    return "cn=" + Rdn.escapeValue(uid) + "," + getAdministratorContainerDN();
1175  }
1176
1177  /**
1178   * Creates an Administrator in the ADS.
1179   *
1180   * @param adminProperties
1181   *          the properties of the administrator to be created.
1182   * @throws ADSContextException
1183   *           if something goes wrong.
1184   */
1185  public void createAdministrator(Map<AdministratorProperty, Object> adminProperties) throws ADSContextException
1186  {
1187    LdapName dnCentralAdmin = makeDNFromAdministratorProperties(adminProperties);
1188    BasicAttributes attrs = makeAttrsFromAdministratorProperties(adminProperties, true, null);
1189
1190    try
1191    {
1192      DirContext ctx = dirContext.createSubcontext(dnCentralAdmin, attrs);
1193      ctx.close();
1194    }
1195    catch (NameAlreadyBoundException x)
1196    {
1197      throw new ADSContextException(ErrorType.ALREADY_REGISTERED);
1198    }
1199    catch (NoPermissionException x)
1200    {
1201      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1202    }
1203    catch (NamingException x)
1204    {
1205      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1206    }
1207  }
1208
1209  /**
1210   * Deletes the administrator in the ADS.
1211   *
1212   * @param adminProperties
1213   *          the properties of the administrator to be deleted.
1214   * @throws ADSContextException
1215   *           if something goes wrong.
1216   */
1217  public void deleteAdministrator(Map<AdministratorProperty, Object> adminProperties) throws ADSContextException
1218  {
1219    LdapName dnCentralAdmin = makeDNFromAdministratorProperties(adminProperties);
1220
1221    try
1222    {
1223      dirContext.destroySubcontext(dnCentralAdmin);
1224    }
1225    catch (NameNotFoundException | NotContextException x)
1226    {
1227      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
1228    }
1229    catch (NoPermissionException x)
1230    {
1231      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1232    }
1233    catch (NamingException x)
1234    {
1235      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1236    }
1237  }
1238
1239  /**
1240   * Updates and administrator registered in the ADS.
1241   *
1242   * @param adminProperties
1243   *          the new properties of the administrator.
1244   * @param newAdminUserId
1245   *          The new admin user Identifier, or null.
1246   * @throws ADSContextException
1247   *           if something goes wrong.
1248   */
1249  public void updateAdministrator(Map<AdministratorProperty, Object> adminProperties, String newAdminUserId)
1250      throws ADSContextException
1251  {
1252    LdapName dnCentralAdmin = makeDNFromAdministratorProperties(adminProperties);
1253
1254    boolean updatePassword = adminProperties.containsKey(AdministratorProperty.PASSWORD);
1255
1256    NamingEnumeration<?> currentPrivileges = null;
1257    try
1258    {
1259      // Entry renaming
1260      if (newAdminUserId != null)
1261      {
1262        Map<AdministratorProperty, Object> newAdminUserProps = new HashMap<>(adminProperties);
1263        newAdminUserProps.put(AdministratorProperty.UID, newAdminUserId);
1264        LdapName newDn = makeDNFromAdministratorProperties(newAdminUserProps);
1265        dirContext.rename(dnCentralAdmin, newDn);
1266        dnCentralAdmin = newDn;
1267        adminProperties.put(AdministratorProperty.UID, newAdminUserId);
1268      }
1269
1270      // if modification includes 'privilege', we have to get first the
1271      // current privileges list.
1272      if (adminProperties.containsKey(AdministratorProperty.PRIVILEGE))
1273      {
1274        SearchControls sc = new SearchControls();
1275        sc.setSearchScope(SearchControls.OBJECT_SCOPE);
1276        String[] attList = { "ds-privilege-name" };
1277        sc.setReturningAttributes(attList);
1278        NamingEnumeration<SearchResult> ne = dirContext.search(dnCentralAdmin, "(objectclass=*)", sc);
1279        try
1280        {
1281          while (ne.hasMore())
1282          {
1283            currentPrivileges = ne.next().getAttributes().get("ds-privilege-name").getAll();
1284          }
1285        }
1286        finally
1287        {
1288          handleCloseNamingEnumeration(ne);
1289        }
1290      }
1291
1292      // Replace properties, if needed.
1293      if (adminProperties.size() > 1)
1294      {
1295        BasicAttributes attrs =
1296            makeAttrsFromAdministratorProperties(adminProperties, updatePassword, currentPrivileges);
1297        dirContext.modifyAttributes(dnCentralAdmin, DirContext.REPLACE_ATTRIBUTE, attrs);
1298      }
1299    }
1300    catch (NameNotFoundException x)
1301    {
1302      throw new ADSContextException(ErrorType.NOT_YET_REGISTERED);
1303    }
1304    catch (NoPermissionException x)
1305    {
1306      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
1307    }
1308    catch (NamingException x)
1309    {
1310      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1311    }
1312    finally
1313    {
1314      handleCloseNamingEnumeration(currentPrivileges);
1315    }
1316  }
1317
1318  /**
1319   * Returns the DN of the suffix that contains the administration data.
1320   *
1321   * @return the DN of the suffix that contains the administration data.
1322   */
1323  public static String getAdministrationSuffixDN()
1324  {
1325    return "cn=admin data";
1326  }
1327
1328  /**
1329   * This method returns the DN of the entry that corresponds to the given host
1330   * name and installation path.
1331   *
1332   * @param hostname
1333   *          the host name.
1334   * @param ipath
1335   *          the installation path.
1336   * @return the DN of the entry that corresponds to the given host name and
1337   *         installation path.
1338   * @throws ADSContextException
1339   *           if something goes wrong.
1340   */
1341  private static LdapName makeDNFromHostnameAndPath(String hostname, String ipath) throws ADSContextException
1342  {
1343    return nameFromDN("cn=" + Rdn.escapeValue(hostname + "@" + ipath) + "," + getServerContainerDN());
1344  }
1345
1346  /**
1347   * This method returns the DN of the entry that corresponds to the given host
1348   * name port representation.
1349   *
1350   * @param serverUniqueId
1351   *          the host name and port.
1352   * @return the DN of the entry that corresponds to the given host name and
1353   *         port.
1354   * @throws ADSContextException
1355   *           if something goes wrong.
1356   */
1357  private static LdapName makeDNFromServerUniqueId(String serverUniqueId) throws ADSContextException
1358  {
1359    return nameFromDN("cn=" + Rdn.escapeValue(serverUniqueId) + "," + getServerContainerDN());
1360  }
1361
1362  /**
1363   * This method returns the DN of the entry that corresponds to the given
1364   * server group properties.
1365   *
1366   * @param serverGroupProperties
1367   *          the server group properties
1368   * @return the DN of the entry that corresponds to the given server group
1369   *         properties.
1370   * @throws ADSContextException
1371   *           if something goes wrong.
1372   */
1373  private static LdapName makeDNFromServerGroupProperties(Map<ServerGroupProperty, Object> serverGroupProperties)
1374      throws ADSContextException
1375  {
1376    String serverGroupId = (String) serverGroupProperties.get(ServerGroupProperty.UID);
1377    if (serverGroupId == null)
1378    {
1379      throw new ADSContextException(ErrorType.MISSING_NAME);
1380    }
1381    return nameFromDN("cn=" + Rdn.escapeValue(serverGroupId) + "," + getServerGroupContainerDN());
1382  }
1383
1384  /**
1385   * This method returns the DN of the entry that corresponds to the given
1386   * server properties.
1387   *
1388   * @param serverProperties
1389   *          the server properties.
1390   * @return the DN of the entry that corresponds to the given server
1391   *         properties.
1392   * @throws ADSContextException
1393   *           if something goes wrong.
1394   */
1395  private static LdapName makeDNFromServerProperties(Map<ServerProperty, Object> serverProperties)
1396      throws ADSContextException
1397  {
1398    String serverID = getServerID(serverProperties);
1399    if (serverID != null)
1400    {
1401      return makeDNFromServerUniqueId(serverID);
1402    }
1403
1404    String hostname = getHostname(serverProperties);
1405    try
1406    {
1407      String ipath = getInstallPath(serverProperties);
1408      return makeDNFromHostnameAndPath(hostname, ipath);
1409    }
1410    catch (ADSContextException ace)
1411    {
1412      ServerDescriptor s = ServerDescriptor.createStandalone(serverProperties);
1413      return makeDNFromServerUniqueId(s.getHostPort(true));
1414    }
1415  }
1416
1417  /**
1418   * This method returns the DN of the entry that corresponds to the given
1419   * server properties.
1420   *
1421   * @param serverProperties
1422   *          the server properties.
1423   * @return the DN of the entry that corresponds to the given server
1424   *         properties.
1425   * @throws ADSContextException
1426   *           if something goes wrong.
1427   */
1428  public static String getServerIdFromServerProperties(Map<ServerProperty, Object> serverProperties)
1429      throws ADSContextException
1430  {
1431    LdapName ldapName = makeDNFromServerProperties(serverProperties);
1432    String rdn = ldapName.get(ldapName.size() - 1);
1433    int pos = rdn.indexOf("=");
1434    return rdn.substring(pos + 1);
1435  }
1436
1437  /**
1438   * This method returns the DN of the entry that corresponds to the given
1439   * administrator properties.
1440   *
1441   * @param adminProperties
1442   *          the administrator properties.
1443   * @return the DN of the entry that corresponds to the given administrator
1444   *         properties.
1445   * @throws ADSContextException
1446   *           if something goes wrong.
1447   */
1448  private static LdapName makeDNFromAdministratorProperties(Map<AdministratorProperty, Object> adminProperties)
1449      throws ADSContextException
1450  {
1451    return makeDNFromAdministratorProperties(getAdministratorUID(adminProperties));
1452  }
1453
1454  /**
1455   * This method returns the DN of the entry that corresponds to the given
1456   * administrator properties.
1457   *
1458   * @param adminUid
1459   *          the administrator uid.
1460   * @return the DN of the entry that corresponds to the given administrator
1461   *         properties.
1462   * @throws ADSContextException
1463   *           if something goes wrong.
1464   */
1465  private static LdapName makeDNFromAdministratorProperties(String adminUid) throws ADSContextException
1466  {
1467    return nameFromDN(getAdministratorDN(adminUid));
1468  }
1469
1470  /**
1471   * Returns the attributes for some administrator properties.
1472   *
1473   * @param adminProperties
1474   *          the administrator properties.
1475   * @param passwordRequired
1476   *          Indicates if the properties should include the password.
1477   * @param currentPrivileges
1478   *          The current privilege list or null.
1479   * @return the attributes for the given administrator properties.
1480   * @throws ADSContextException
1481   *           if something goes wrong.
1482   */
1483  private static BasicAttributes makeAttrsFromAdministratorProperties(
1484      Map<AdministratorProperty, Object> adminProperties, boolean passwordRequired,
1485      NamingEnumeration<?> currentPrivileges) throws ADSContextException
1486  {
1487    BasicAttributes attrs = new BasicAttributes();
1488    Attribute oc = new BasicAttribute("objectclass");
1489    if (passwordRequired)
1490    {
1491      attrs.put("userPassword", getAdministratorPassword(adminProperties));
1492    }
1493    oc.add("top");
1494    oc.add("person");
1495    attrs.put(oc);
1496    attrs.put("sn", GLOBAL_ADMIN_UID);
1497    if (adminProperties.containsKey(AdministratorProperty.DESCRIPTION))
1498    {
1499      attrs.put("description", adminProperties.get(AdministratorProperty.DESCRIPTION));
1500    }
1501    Attribute privilegeAtt;
1502    if (adminProperties.containsKey(AdministratorProperty.PRIVILEGE))
1503    {
1504      // We assume that privilege strings provided in
1505      // AdministratorProperty.PRIVILEGE
1506      // are valid privileges represented as a LinkedList of string.
1507      privilegeAtt = new BasicAttribute("ds-privilege-name");
1508      if (currentPrivileges != null)
1509      {
1510        while (currentPrivileges.hasMoreElements())
1511        {
1512          privilegeAtt.add(currentPrivileges.nextElement().toString());
1513        }
1514      }
1515
1516      LinkedList<?> privileges = (LinkedList<?>) adminProperties.get(AdministratorProperty.PRIVILEGE);
1517      for (Object o : privileges)
1518      {
1519        String p = o.toString();
1520        if (p.startsWith("-"))
1521        {
1522          privilegeAtt.remove(p.substring(1));
1523        }
1524        else
1525        {
1526          privilegeAtt.add(p);
1527        }
1528      }
1529    }
1530    else
1531    {
1532      privilegeAtt = addRootPrivileges();
1533    }
1534    attrs.put(privilegeAtt);
1535
1536    // Add the RootDNs Password policy so the password do not expire.
1537    attrs.put("ds-pwp-password-policy-dn", "cn=Root Password Policy,cn=Password Policies,cn=config");
1538
1539    return attrs;
1540  }
1541
1542  /**
1543   * Builds an attribute which contains 'root' privileges.
1544   *
1545   * @return The attribute which contains 'root' privileges.
1546   */
1547  private static Attribute addRootPrivileges()
1548  {
1549    Attribute privilege = new BasicAttribute("ds-privilege-name");
1550    privilege.add("bypass-acl");
1551    privilege.add("modify-acl");
1552    privilege.add("config-read");
1553    privilege.add("config-write");
1554    privilege.add("ldif-import");
1555    privilege.add("ldif-export");
1556    privilege.add("backend-backup");
1557    privilege.add("backend-restore");
1558    privilege.add("server-shutdown");
1559    privilege.add("server-restart");
1560    privilege.add("disconnect-client");
1561    privilege.add("cancel-request");
1562    privilege.add("password-reset");
1563    privilege.add("update-schema");
1564    privilege.add("privilege-change");
1565    privilege.add("unindexed-search");
1566    privilege.add("subentry-write");
1567    privilege.add("changelog-read");
1568    return privilege;
1569  }
1570
1571  /**
1572   * Returns the attributes for some server properties.
1573   *
1574   * @param serverProperties
1575   *          the server properties.
1576   * @param addObjectClass
1577   *          Indicates if the object class has to be added.
1578   * @return the attributes for the given server properties.
1579   */
1580  private static BasicAttributes makeAttrsFromServerProperties(Map<ServerProperty, Object> serverProperties,
1581      boolean addObjectClass)
1582  {
1583    BasicAttributes result = new BasicAttributes();
1584
1585    // Transform 'properties' into 'attributes'
1586    for (ServerProperty prop : serverProperties.keySet())
1587    {
1588      Attribute attr = makeAttrFromServerProperty(prop, serverProperties.get(prop));
1589      if (attr != null)
1590      {
1591        result.put(attr);
1592      }
1593    }
1594    if (addObjectClass)
1595    {
1596      // Add the objectclass attribute value
1597      // TODO: use another structural objectclass
1598      Attribute oc = new BasicAttribute("objectclass");
1599      oc.add("top");
1600      oc.add("ds-cfg-branch");
1601      oc.add("extensibleobject");
1602      result.put(oc);
1603    }
1604    return result;
1605  }
1606
1607  /**
1608   * Returns the attribute for a given server property.
1609   *
1610   * @param property
1611   *          the server property.
1612   * @param value
1613   *          the value.
1614   * @return the attribute for a given server property.
1615   */
1616  private static Attribute makeAttrFromServerProperty(ServerProperty property, Object value)
1617  {
1618    Attribute result;
1619
1620    switch (property)
1621    {
1622    case INSTANCE_PUBLIC_KEY_CERTIFICATE:
1623      result = null; // used in separate instance key entry
1624      break;
1625    case GROUPS:
1626      result = new BasicAttribute(ServerProperty.GROUPS.getAttributeName());
1627      for (Object o : ((Set<?>) value))
1628      {
1629        result.add(o);
1630      }
1631      break;
1632    default:
1633      result = new BasicAttribute(property.getAttributeName(), value);
1634    }
1635    return result;
1636  }
1637
1638  /**
1639   * Returns the attributes for some server group properties.
1640   *
1641   * @param serverGroupProperties
1642   *          the server group properties.
1643   * @return the attributes for the given server group properties.
1644   */
1645  private static BasicAttributes makeAttrsFromServerGroupProperties(
1646      Map<ServerGroupProperty, Object> serverGroupProperties)
1647  {
1648    BasicAttributes result = new BasicAttributes();
1649
1650    // Transform 'properties' into 'attributes'
1651    for (ServerGroupProperty prop : serverGroupProperties.keySet())
1652    {
1653      Attribute attr = makeAttrFromServerGroupProperty(prop, serverGroupProperties.get(prop));
1654      if (attr != null)
1655      {
1656        result.put(attr);
1657      }
1658    }
1659    return result;
1660  }
1661
1662  /**
1663   * Returns the attributes for some server group properties.
1664   *
1665   * @param serverGroupProperties
1666   *          the server group properties.
1667   * @return the attributes for the given server group properties.
1668   */
1669  private static BasicAttributes makeAttrsFromServerGroupProperties(Set<ServerGroupProperty> serverGroupProperties)
1670  {
1671    BasicAttributes result = new BasicAttributes();
1672
1673    // Transform 'properties' into 'attributes'
1674    for (ServerGroupProperty prop : serverGroupProperties)
1675    {
1676      Attribute attr = makeAttrFromServerGroupProperty(prop, null);
1677      if (attr != null)
1678      {
1679        result.put(attr);
1680      }
1681    }
1682    return result;
1683  }
1684
1685  /**
1686   * Returns the attribute for a given server group property.
1687   *
1688   * @param property
1689   *          the server group property.
1690   * @param value
1691   *          the value.
1692   * @return the attribute for a given server group property.
1693   */
1694  private static Attribute makeAttrFromServerGroupProperty(ServerGroupProperty property, Object value)
1695  {
1696    switch (property)
1697    {
1698    case MEMBERS:
1699      Attribute result = new BasicAttribute(ServerGroupProperty.MEMBERS.getAttributeName());
1700      for (Object o : ((Set<?>) value))
1701      {
1702        result.add(o);
1703      }
1704      return result;
1705    default:
1706      return new BasicAttribute(property.getAttributeName(), value);
1707    }
1708  }
1709
1710  /**
1711   * Returns the properties of a server group for some LDAP attributes.
1712   *
1713   * @param attrs
1714   *          the LDAP attributes.
1715   * @return the properties of a server group for some LDAP attributes.
1716   * @throws ADSContextException
1717   *           if something goes wrong.
1718   */
1719  private Map<ServerGroupProperty, Object> makePropertiesFromServerGroupAttrs(Attributes attrs)
1720      throws ADSContextException
1721  {
1722    Map<ServerGroupProperty, Object> result = new HashMap<>();
1723    try
1724    {
1725      for (ServerGroupProperty prop : ServerGroupProperty.values())
1726      {
1727        Attribute attr = attrs.get(prop.getAttributeName());
1728        if (attr == null)
1729        {
1730          continue;
1731        }
1732        Object value;
1733
1734        if (attr.size() >= 1 && MULTIVALUED_SERVER_GROUP_PROPERTIES.contains(prop))
1735        {
1736          Set<String> set = new HashSet<>();
1737          NamingEnumeration<?> ae = attr.getAll();
1738          try
1739          {
1740            while (ae.hasMore())
1741            {
1742              set.add((String) ae.next());
1743            }
1744          }
1745          finally
1746          {
1747            ae.close();
1748          }
1749          value = set;
1750        }
1751        else
1752        {
1753          value = attr.get(0);
1754        }
1755
1756        result.put(prop, value);
1757      }
1758    }
1759    catch (NamingException x)
1760    {
1761      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1762    }
1763    return result;
1764  }
1765
1766  /**
1767   * Returns the properties of a server for some LDAP attributes.
1768   *
1769   * @param attrs
1770   *          the LDAP attributes.
1771   * @return the properties of a server for some LDAP attributes.
1772   * @throws ADSContextException
1773   *           if something goes wrong.
1774   */
1775  private Map<ServerProperty, Object> makePropertiesFromServerAttrs(Attributes attrs) throws ADSContextException
1776  {
1777    Map<ServerProperty, Object> result = new HashMap<>();
1778    try
1779    {
1780      NamingEnumeration<? extends Attribute> ne = attrs.getAll();
1781      while (ne.hasMore())
1782      {
1783        Attribute attr = ne.next();
1784        String attrID = attr.getID();
1785        Object value;
1786
1787        if (attrID.endsWith(";binary"))
1788        {
1789          attrID = attrID.substring(0, attrID.lastIndexOf(";binary"));
1790        }
1791
1792        ServerProperty prop = null;
1793        ServerProperty[] props = ServerProperty.values();
1794        for (int i = 0; i < props.length && prop == null; i++)
1795        {
1796          String v = props[i].getAttributeName();
1797          if (attrID.equalsIgnoreCase(v))
1798          {
1799            prop = props[i];
1800          }
1801        }
1802        if (prop == null)
1803        {
1804          // Do not handle it
1805        }
1806        else
1807        {
1808          if (attr.size() >= 1 && MULTIVALUED_SERVER_PROPERTIES.contains(prop))
1809          {
1810            Set<String> set = new HashSet<>();
1811            NamingEnumeration<?> ae = attr.getAll();
1812            try
1813            {
1814              while (ae.hasMore())
1815              {
1816                set.add((String) ae.next());
1817              }
1818            }
1819            finally
1820            {
1821              ae.close();
1822            }
1823            value = set;
1824          }
1825          else
1826          {
1827            value = attr.get(0);
1828          }
1829
1830          result.put(prop, value);
1831        }
1832      }
1833    }
1834    catch (NamingException x)
1835    {
1836      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1837    }
1838    return result;
1839  }
1840
1841  /**
1842   * Returns the properties of an administrator for some rdn and LDAP
1843   * attributes.
1844   *
1845   * @param rdn
1846   *          the RDN.
1847   * @param attrs
1848   *          the LDAP attributes.
1849   * @return the properties of an administrator for the given rdn and LDAP
1850   *         attributes.
1851   * @throws ADSContextException
1852   *           if something goes wrong.
1853   */
1854  private Map<AdministratorProperty, Object> makePropertiesFromAdministratorAttrs(String rdn, Attributes attrs)
1855      throws ADSContextException
1856  {
1857    Map<AdministratorProperty, Object> result = new HashMap<>();
1858    LdapName nameObj;
1859    nameObj = nameFromDN(rdn);
1860    String dn = nameObj + "," + getAdministratorContainerDN();
1861    result.put(AdministratorProperty.ADMINISTRATOR_DN, dn);
1862    NamingEnumeration<? extends Attribute> ne = null;
1863    try
1864    {
1865      ne = attrs.getAll();
1866      while (ne.hasMore())
1867      {
1868        Attribute attr = ne.next();
1869        String attrID = attr.getID();
1870        Object value;
1871
1872        if ("cn".equalsIgnoreCase(attrID))
1873        {
1874          value = attr.get(0);
1875          result.put(AdministratorProperty.UID, value);
1876        }
1877        else if ("userpassword".equalsIgnoreCase(attrID))
1878        {
1879          value = new String((byte[]) attr.get());
1880          result.put(AdministratorProperty.PASSWORD, value);
1881        }
1882        else if ("description".equalsIgnoreCase(attrID))
1883        {
1884          value = attr.get(0);
1885          result.put(AdministratorProperty.DESCRIPTION, value);
1886        }
1887        else if ("ds-privilege-name".equalsIgnoreCase(attrID))
1888        {
1889          LinkedHashSet<String> privileges = new LinkedHashSet<>();
1890          NamingEnumeration<?> attValueList = attr.getAll();
1891          while (attValueList.hasMoreElements())
1892          {
1893            privileges.add(attValueList.next().toString());
1894          }
1895          result.put(AdministratorProperty.PRIVILEGE, privileges);
1896        }
1897      }
1898    }
1899    catch (NamingException x)
1900    {
1901      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
1902    }
1903    finally
1904    {
1905      handleCloseNamingEnumeration(ne);
1906    }
1907
1908    return result;
1909  }
1910
1911  /**
1912   * Returns the parent entry of the server entries.
1913   *
1914   * @return the parent entry of the server entries.
1915   */
1916  public static String getServerContainerDN()
1917  {
1918    return "cn=Servers," + getAdministrationSuffixDN();
1919  }
1920
1921  /**
1922   * Returns the parent entry of the administrator entries.
1923   *
1924   * @return the parent entry of the administrator entries.
1925   */
1926  public static String getAdministratorContainerDN()
1927  {
1928    return "cn=Administrators," + getAdministrationSuffixDN();
1929  }
1930
1931  /**
1932   * Returns the parent entry of the server group entries.
1933   *
1934   * @return the parent entry of the server group entries.
1935   */
1936  public static String getServerGroupContainerDN()
1937  {
1938    return "cn=Server Groups," + getAdministrationSuffixDN();
1939  }
1940
1941  /**
1942   * Returns the all server group entry DN.
1943   *
1944   * @return the all server group entry DN.
1945   */
1946  private static String getAllServerGroupDN()
1947  {
1948    return "cn=" + Rdn.escapeValue(ALL_SERVERGROUP_NAME) + "," + getServerGroupContainerDN();
1949  }
1950
1951  /**
1952   * Returns the host name for the given properties.
1953   *
1954   * @param serverProperties
1955   *          the server properties.
1956   * @return the host name for the given properties.
1957   * @throws ADSContextException
1958   *           if the host name could not be found or its value is not valid.
1959   */
1960  private static String getHostname(Map<ServerProperty, Object> serverProperties) throws ADSContextException
1961  {
1962    String result = (String) serverProperties.get(ServerProperty.HOST_NAME);
1963    if (result == null)
1964    {
1965      throw new ADSContextException(ErrorType.MISSING_HOSTNAME);
1966    }
1967    else if (result.length() == 0)
1968    {
1969      throw new ADSContextException(ErrorType.NOVALID_HOSTNAME);
1970    }
1971    return result;
1972  }
1973
1974  /**
1975   * Returns the Server ID for the given properties.
1976   *
1977   * @param serverProperties
1978   *          the server properties.
1979   * @return the server ID for the given properties or null.
1980   */
1981  private static String getServerID(Map<ServerProperty, Object> serverProperties)
1982  {
1983    String result = (String) serverProperties.get(ServerProperty.ID);
1984    if (result != null && result.length() == 0)
1985    {
1986      result = null;
1987    }
1988    return result;
1989  }
1990
1991  /**
1992   * Returns the install path for the given properties.
1993   *
1994   * @param serverProperties
1995   *          the server properties.
1996   * @return the install path for the given properties.
1997   * @throws ADSContextException
1998   *           if the install path could not be found or its value is not valid.
1999   */
2000  private static String getInstallPath(Map<ServerProperty, Object> serverProperties) throws ADSContextException
2001  {
2002    String result = (String) serverProperties.get(ServerProperty.INSTANCE_PATH);
2003    if (result == null)
2004    {
2005      throw new ADSContextException(ErrorType.MISSING_IPATH);
2006    }
2007    else if (result.length() == 0)
2008    {
2009      throw new ADSContextException(ErrorType.NOVALID_IPATH);
2010    }
2011    return result;
2012  }
2013
2014  /**
2015   * Returns the Administrator UID for the given properties.
2016   *
2017   * @param adminProperties
2018   *          the server properties.
2019   * @return the Administrator UID for the given properties.
2020   * @throws ADSContextException
2021   *           if the administrator UID could not be found.
2022   */
2023  private static String getAdministratorUID(Map<AdministratorProperty, Object> adminProperties)
2024      throws ADSContextException
2025  {
2026    String result = (String) adminProperties.get(AdministratorProperty.UID);
2027    if (result == null)
2028    {
2029      throw new ADSContextException(ErrorType.MISSING_ADMIN_UID);
2030    }
2031    return result;
2032  }
2033
2034  /**
2035   * Returns the Administrator password for the given properties.
2036   *
2037   * @param adminProperties
2038   *          the server properties.
2039   * @return the Administrator password for the given properties.
2040   * @throws ADSContextException
2041   *           if the administrator password could not be found.
2042   */
2043  private static String getAdministratorPassword(Map<AdministratorProperty, Object> adminProperties)
2044      throws ADSContextException
2045  {
2046    String result = (String) adminProperties.get(AdministratorProperty.PASSWORD);
2047    if (result == null)
2048    {
2049      throw new ADSContextException(ErrorType.MISSING_ADMIN_PASSWORD);
2050    }
2051    return result;
2052  }
2053
2054  // LDAP utilities
2055  /**
2056   * Returns the LdapName object for the given dn.
2057   *
2058   * @param dn
2059   *          the DN.
2060   * @return the LdapName object for the given dn.
2061   * @throws ADSContextException
2062   *           if a valid LdapName could not be retrieved for the given dn.
2063   */
2064  private static LdapName nameFromDN(String dn) throws ADSContextException
2065  {
2066    try
2067    {
2068      return new LdapName(dn);
2069    }
2070    catch (InvalidNameException x)
2071    {
2072      logger.error(LocalizableMessage.raw("Error parsing dn " + dn, x));
2073      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2074    }
2075  }
2076
2077  /**
2078   * Returns the String rdn for the given search result name.
2079   *
2080   * @param rdnName
2081   *          the search result name.
2082   * @return the String rdn for the given search result name.
2083   * @throws ADSContextException
2084   *           if a valid String rdn could not be retrieved for the given result
2085   *           name.
2086   */
2087  private static String getRdn(String rdnName) throws ADSContextException
2088  {
2089    // Transform the JNDI name into a RDN string
2090    try
2091    {
2092      return new CompositeName(rdnName).get(0);
2093    }
2094    catch (InvalidNameException x)
2095    {
2096      logger.error(LocalizableMessage.raw("Error parsing rdn " + rdnName, x));
2097      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2098    }
2099  }
2100
2101  /**
2102   * Tells whether an entry with the provided DN exists.
2103   *
2104   * @param dn
2105   *          the DN to check.
2106   * @return <CODE>true</CODE> if the entry exists and <CODE>false</CODE> if it
2107   *         does not.
2108   * @throws ADSContextException
2109   *           if an error occurred while checking if the entry exists or not.
2110   */
2111  private boolean isExistingEntry(LdapName dn) throws ADSContextException
2112  {
2113    try
2114    {
2115      SearchControls sc = new SearchControls();
2116
2117      sc.setSearchScope(SearchControls.OBJECT_SCOPE);
2118      sc.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES });
2119      NamingEnumeration<SearchResult> sr = getDirContext().search(dn, "(objectclass=*)", sc);
2120      boolean result = false;
2121      try
2122      {
2123        while (sr.hasMore())
2124        {
2125          sr.next();
2126          result = true;
2127        }
2128      }
2129      finally
2130      {
2131        sr.close();
2132      }
2133      return result;
2134    }
2135    catch (NameNotFoundException x)
2136    {
2137      return false;
2138    }
2139    catch (NoPermissionException x)
2140    {
2141      throw new ADSContextException(ErrorType.ACCESS_PERMISSION);
2142    }
2143    catch (javax.naming.NamingException x)
2144    {
2145      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2146    }
2147  }
2148
2149  /**
2150   * Creates a container entry with the given dn.
2151   *
2152   * @param dn
2153   *          the entry of the new entry to be created.
2154   * @throws ADSContextException
2155   *           if the entry could not be created.
2156   */
2157  private void createContainerEntry(String dn) throws ADSContextException
2158  {
2159    BasicAttributes attrs = new BasicAttributes();
2160    Attribute oc = new BasicAttribute("objectclass");
2161    oc.add("top");
2162    oc.add("ds-cfg-branch");
2163    attrs.put(oc);
2164    createEntry(dn, attrs);
2165  }
2166
2167  /**
2168   * Creates the administrator container entry.
2169   *
2170   * @throws ADSContextException
2171   *           if the entry could not be created.
2172   */
2173  private void createAdministratorContainerEntry() throws ADSContextException
2174  {
2175    BasicAttributes attrs = new BasicAttributes();
2176    Attribute oc = new BasicAttribute("objectclass");
2177    oc.add("groupofurls");
2178    attrs.put(oc);
2179    attrs.put("memberURL", "ldap:///" + getAdministratorContainerDN() + "??one?(objectclass=*)");
2180    attrs.put("description", "Group of identities which have full access.");
2181    createEntry(getAdministratorContainerDN(), attrs);
2182  }
2183
2184  /**
2185   * Creates the top container entry.
2186   *
2187   * @throws ADSContextException
2188   *           if the entry could not be created.
2189   */
2190  private void createTopContainerEntry() throws ADSContextException
2191  {
2192    BasicAttributes attrs = new BasicAttributes();
2193    Attribute oc = new BasicAttribute("objectclass");
2194    oc.add("top");
2195    oc.add("ds-cfg-branch");
2196    attrs.put(oc);
2197    createEntry(getAdministrationSuffixDN(), attrs);
2198  }
2199
2200  /**
2201   * Creates an entry with the provided dn and attributes.
2202   *
2203   * @param dn
2204   *          the dn of the entry.
2205   * @param attrs
2206   *          the attributes of the entry.
2207   * @throws ADSContextException
2208   *           if the entry could not be created.
2209   */
2210  private void createEntry(String dn, Attributes attrs) throws ADSContextException
2211  {
2212    try
2213    {
2214      DirContext ctx = getDirContext().createSubcontext(nameFromDN(dn), attrs);
2215      ctx.close();
2216    }
2217    catch (NamingException x)
2218    {
2219      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2220    }
2221  }
2222
2223  /**
2224   * Creates the Administration Suffix.
2225   *
2226   * @param backendName
2227   *          the backend name to be used for the Administration Suffix. If this
2228   *          value is null the default backendName for the Administration
2229   *          Suffix will be used.
2230   * @throws ADSContextException
2231   *           if something goes wrong.
2232   */
2233  public void createAdministrationSuffix(String backendName) throws ADSContextException
2234  {
2235    ADSContextHelper helper = new ADSContextHelper();
2236    String ben = backendName;
2237    if (backendName == null)
2238    {
2239      ben = getDefaultBackendName();
2240    }
2241    helper.createAdministrationSuffix(getDirContext(), ben);
2242  }
2243
2244  /**
2245   * Returns the default backend name of the administration data.
2246   *
2247   * @return the default backend name of the administration data.
2248   */
2249  public static String getDefaultBackendName()
2250  {
2251    return "adminRoot";
2252  }
2253
2254  /**
2255   * Returns the LDIF file of the administration data.
2256   *
2257   * @return the LDIF file of the administration data.
2258   */
2259  public static String getAdminLDIFFile()
2260  {
2261    return "config" + File.separator + "admin-backend.ldif";
2262  }
2263
2264  /** CryptoManager related types, fields, and methods. */
2265
2266  /**
2267   * Returns the parent entry of the server key entries in ADS.
2268   *
2269   * @return the parent entry of the server key entries in ADS.
2270   */
2271  public static String getInstanceKeysContainerDN()
2272  {
2273    return "cn=instance keys," + getAdministrationSuffixDN();
2274  }
2275
2276  /**
2277   * Returns the parent entry of the secret key entries in ADS.
2278   *
2279   * @return the parent entry of the secret key entries in ADS.
2280   */
2281  public static String getSecretKeysContainerDN()
2282  {
2283    return "cn=secret keys," + getAdministrationSuffixDN();
2284  }
2285
2286  /**
2287   * Tells whether the provided server is registered in the registry.
2288   *
2289   * @param server
2290   *          the server.
2291   * @param registry
2292   *          the registry.
2293   * @return <CODE>true</CODE> if the server is registered in the registry and
2294   *         <CODE>false</CODE> otherwise.
2295   */
2296  public static boolean isRegistered(ServerDescriptor server, Set<Map<ADSContext.ServerProperty, Object>> registry)
2297  {
2298    for (Map<ADSContext.ServerProperty, Object> s : registry)
2299    {
2300      ServerDescriptor servInRegistry = ServerDescriptor.createStandalone(s);
2301      if (servInRegistry.getId().equals(server.getId()))
2302      {
2303        return true;
2304      }
2305    }
2306    return false;
2307  }
2308
2309  /**
2310   * Register instance key-pair public-key certificate provided in
2311   * serverProperties: generate a key-id attribute if one is not provided (as
2312   * expected); add an instance key public-key certificate entry for the key
2313   * certificate; and associate the certificate entry with the server entry via
2314   * the key ID attribute.
2315   *
2316   * @param serverProperties
2317   *          Properties of the server being registered to which the instance
2318   *          key entry belongs.
2319   * @param serverEntryDn
2320   *          The server's ADS entry DN.
2321   * @throws NamingException
2322   *           In case some JNDI operation fails.
2323   * @throws CryptoManager.CryptoManagerException
2324   *           In case there is a problem getting the instance public key
2325   *           certificate ID.
2326   */
2327  private void registerInstanceKeyCertificate(Map<ServerProperty, Object> serverProperties, LdapName serverEntryDn)
2328      throws ADSContextException
2329  {
2330    ADSContextHelper helper = new ADSContextHelper();
2331    helper.registerInstanceKeyCertificate(dirContext, serverProperties, serverEntryDn);
2332  }
2333
2334  /**
2335   * Return the set of valid (i.e., not tagged as compromised) instance key-pair
2336   * public-key certificate entries in ADS. NOTE: calling this method assumes
2337   * that all the jar files are present in the classpath.
2338   *
2339   * @return The set of valid (i.e., not tagged as compromised) instance
2340   *         key-pair public-key certificate entries in ADS represented as a Map
2341   *         from ds-cfg-key-id value to ds-cfg-public-key-certificate;binary
2342   *         value. Note that the collection might be empty.
2343   * @throws ADSContextException
2344   *           in case of problems with the entry search.
2345   * @see org.opends.server.crypto.CryptoManagerImpl#getTrustedCertificates
2346   */
2347  public Map<String, byte[]> getTrustedCertificates() throws ADSContextException
2348  {
2349    final Map<String, byte[]> certificateMap = new HashMap<>();
2350    final String baseDNStr = getInstanceKeysContainerDN();
2351    try
2352    {
2353      ADSContextHelper helper = new ADSContextHelper();
2354      final LdapName baseDN = new LdapName(baseDNStr);
2355      final String FILTER_OC_INSTANCE_KEY = "(objectclass=" + helper.getOcCryptoInstanceKey() + ")";
2356      final String FILTER_NOT_COMPROMISED = "(!(" + helper.getAttrCryptoKeyCompromisedTime() + "=*))";
2357      final String searchFilter = "(&" + FILTER_OC_INSTANCE_KEY + FILTER_NOT_COMPROMISED + ")";
2358      final SearchControls searchControls = new SearchControls();
2359      searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
2360      final String attrIDs[] =
2361          { ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName(),
2362            ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE.getAttributeName() + ";binary" };
2363      searchControls.setReturningAttributes(attrIDs);
2364      NamingEnumeration<SearchResult> keyEntries = dirContext.search(baseDN, searchFilter, searchControls);
2365      try
2366      {
2367        while (keyEntries.hasMore())
2368        {
2369          final SearchResult entry = keyEntries.next();
2370          final Attributes attrs = entry.getAttributes();
2371          final Attribute keyIDAttr = attrs.get(attrIDs[0]);
2372          final Attribute keyCertAttr = attrs.get(attrIDs[1]);
2373          if (null == keyIDAttr || null == keyCertAttr)
2374          {
2375            continue;// schema viol.
2376          }
2377          certificateMap.put((String) keyIDAttr.get(), (byte[]) keyCertAttr.get());
2378        }
2379      }
2380      finally
2381      {
2382        try
2383        {
2384          keyEntries.close();
2385        }
2386        catch (Exception ex)
2387        {
2388          logger.warn(LocalizableMessage.raw("Unexpected error closing enumeration on ADS key pairs", ex));
2389        }
2390      }
2391    }
2392    catch (NamingException x)
2393    {
2394      throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x);
2395    }
2396    return certificateMap;
2397  }
2398
2399  /**
2400   * Merge the contents of this ADSContext with the contents of the provided
2401   * ADSContext. Note that only the contents of this ADSContext will be updated.
2402   *
2403   * @param adsCtx
2404   *          the other ADSContext to merge the contents with.
2405   * @throws ADSContextException
2406   *           if there was an error during the merge.
2407   */
2408  public void mergeWithRegistry(ADSContext adsCtx) throws ADSContextException
2409  {
2410    try
2411    {
2412      mergeAdministrators(adsCtx);
2413      mergeServerGroups(adsCtx);
2414      mergeServers(adsCtx);
2415    }
2416    catch (ADSContextException adce)
2417    {
2418      LocalizableMessage msg = ERR_ADS_MERGE.get(ConnectionUtils.getHostPort(getDirContext()),
2419          ConnectionUtils.getHostPort(adsCtx.getDirContext()), adce.getMessageObject());
2420      throw new ADSContextException(ErrorType.ERROR_MERGING, msg, adce);
2421    }
2422  }
2423
2424  /**
2425   * Merge the administrator contents of this ADSContext with the contents of
2426   * the provided ADSContext. Note that only the contents of this ADSContext
2427   * will be updated.
2428   *
2429   * @param adsCtx
2430   *          the other ADSContext to merge the contents with.
2431   * @throws ADSContextException
2432   *           if there was an error during the merge.
2433   */
2434  private void mergeAdministrators(ADSContext adsCtx) throws ADSContextException
2435  {
2436    Set<Map<AdministratorProperty, Object>> admins2 = adsCtx.readAdministratorRegistry();
2437    SortedSet<String> notDefinedAdmins = new TreeSet<>();
2438    for (Map<AdministratorProperty, Object> admin2 : admins2)
2439    {
2440      String uid = (String) admin2.get(AdministratorProperty.UID);
2441      if (!isAdministratorAlreadyRegistered(uid))
2442      {
2443        notDefinedAdmins.add(uid);
2444      }
2445    }
2446    if (!notDefinedAdmins.isEmpty())
2447    {
2448      LocalizableMessage msg = ERR_ADS_ADMINISTRATOR_MERGE.get(
2449          ConnectionUtils.getHostPort(adsCtx.getDirContext()), ConnectionUtils.getHostPort(getDirContext()),
2450          joinAsString(Constants.LINE_SEPARATOR, notDefinedAdmins), ConnectionUtils.getHostPort(getDirContext()));
2451      throw new ADSContextException(ErrorType.ERROR_MERGING, msg, null);
2452    }
2453  }
2454
2455  /**
2456   * Merge the groups contents of this ADSContext with the contents of the
2457   * provided ADSContext. Note that only the contents of this ADSContext will be
2458   * updated.
2459   *
2460   * @param adsCtx
2461   *          the other ADSContext to merge the contents with.
2462   * @throws ADSContextException
2463   *           if there was an error during the merge.
2464   */
2465  private void mergeServerGroups(ADSContext adsCtx) throws ADSContextException
2466  {
2467    Set<Map<ServerGroupProperty, Object>> serverGroups1 = readServerGroupRegistry();
2468    Set<Map<ServerGroupProperty, Object>> serverGroups2 = adsCtx.readServerGroupRegistry();
2469
2470    for (Map<ServerGroupProperty, Object> group2 : serverGroups2)
2471    {
2472      Map<ServerGroupProperty, Object> group1 = null;
2473      String uid2 = (String) group2.get(ServerGroupProperty.UID);
2474      for (Map<ServerGroupProperty, Object> gr : serverGroups1)
2475      {
2476        String uid1 = (String) gr.get(ServerGroupProperty.UID);
2477        if (uid1.equalsIgnoreCase(uid2))
2478        {
2479          group1 = gr;
2480          break;
2481        }
2482      }
2483
2484      if (group1 != null)
2485      {
2486        // Merge the members, keep the description on this ADS.
2487        Set<String> member1List = getServerGroupMemberList(uid2);
2488        if (member1List == null)
2489        {
2490          member1List = new HashSet<>();
2491        }
2492        Set<String> member2List = adsCtx.getServerGroupMemberList(uid2);
2493        if (member2List != null && !member2List.isEmpty())
2494        {
2495          member1List.addAll(member2List);
2496          Map<ServerGroupProperty, Object> newProperties = new HashMap<>();
2497          newProperties.put(ServerGroupProperty.MEMBERS, member1List);
2498          updateServerGroup(uid2, newProperties);
2499        }
2500      }
2501      else
2502      {
2503        createServerGroup(group2);
2504      }
2505    }
2506  }
2507
2508  /**
2509   * Merge the server contents of this ADSContext with the contents of the
2510   * provided ADSContext. Note that only the contents of this ADSContext will be
2511   * updated.
2512   *
2513   * @param adsCtx
2514   *          the other ADSContext to merge the contents with.
2515   * @throws ADSContextException
2516   *           if there was an error during the merge.
2517   */
2518  private void mergeServers(ADSContext adsCtx) throws ADSContextException
2519  {
2520    for (Map<ServerProperty, Object> server2 : adsCtx.readServerRegistry())
2521    {
2522      if (!isServerAlreadyRegistered(server2))
2523      {
2524        registerServer(server2);
2525      }
2526    }
2527  }
2528
2529  private void handleCloseNamingEnumeration(NamingEnumeration<?> ne) throws ADSContextException
2530  {
2531    if (ne != null)
2532    {
2533      try
2534      {
2535        ne.close();
2536      }
2537      catch (NamingException ex)
2538      {
2539        throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, ex);
2540      }
2541    }
2542  }
2543}