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