/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2007-2010 Sun Microsystems, Inc. * Portions Copyright 2012-2016 ForgeRock AS. */ package org.opends.admin.ads; import static org.forgerock.opendj.ldap.Filter.*; import static org.forgerock.opendj.ldap.ModificationType.*; import static org.forgerock.opendj.ldap.SearchScope.*; import static org.forgerock.opendj.ldap.requests.Requests.*; import static org.forgerock.util.Utils.*; import static org.opends.messages.QuickSetupMessages.*; import static org.opends.server.schema.SchemaConstants.*; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.naming.ldap.Rdn; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.AuthorizationException; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.Connection; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.EntryNotFoundException; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.opendj.ldap.LinkedAttribute; import org.forgerock.opendj.ldap.Modification; import org.forgerock.opendj.ldap.ModificationType; import org.forgerock.opendj.ldap.RDN; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl; import org.forgerock.opendj.ldap.requests.AddRequest; import org.forgerock.opendj.ldap.requests.DeleteRequest; import org.forgerock.opendj.ldap.requests.ModifyRequest; import org.forgerock.opendj.ldap.requests.SearchRequest; import org.forgerock.opendj.ldap.responses.Result; import org.forgerock.opendj.ldap.responses.SearchResultEntry; import org.forgerock.opendj.ldap.schema.AttributeType; import org.forgerock.opendj.ldif.ConnectionEntryReader; import org.opends.admin.ads.ADSContextException.ErrorType; import org.opends.admin.ads.util.ConnectionWrapper; import org.opends.quicksetup.Constants; import org.opends.server.types.HostPort; /** Class used to update and read the contents of the Administration Data. */ public class ADSContext { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * Enumeration containing the different server properties syntaxes that could * be stored in the ADS. */ public enum ADSPropertySyntax { /** String syntax. */ STRING, /** Integer syntax. */ INTEGER, /** Boolean syntax. */ BOOLEAN, /** Certificate;binary syntax. */ CERTIFICATE_BINARY } /** Enumeration containing the different server properties that are stored in the ADS. */ public enum ServerProperty { /** The ID used to identify the server (hostname + port). */ ID("id",ADSPropertySyntax.STRING), /** The host name of the server. */ HOST_NAME("hostname",ADSPropertySyntax.STRING), /** The LDAP port of the server. */ LDAP_PORT("ldapport",ADSPropertySyntax.INTEGER), /** The JMX port of the server. */ JMX_PORT("jmxport",ADSPropertySyntax.INTEGER), /** The JMX secure port of the server. */ JMXS_PORT("jmxsport",ADSPropertySyntax.INTEGER), /** The LDAPS port of the server. */ LDAPS_PORT("ldapsport",ADSPropertySyntax.INTEGER), /** The administration connector port of the server. */ ADMIN_PORT("adminport",ADSPropertySyntax.INTEGER), /** The certificate used by the server. */ CERTIFICATE("certificate",ADSPropertySyntax.STRING), /** The path where the server is installed. */ INSTANCE_PATH("instancepath",ADSPropertySyntax.STRING), /** The description of the server. */ DESCRIPTION("description",ADSPropertySyntax.STRING), /** The OS of the machine where the server is installed. */ HOST_OS("os",ADSPropertySyntax.STRING), /** Whether LDAP is enabled or not. */ LDAP_ENABLED("ldapEnabled",ADSPropertySyntax.BOOLEAN), /** Whether LDAPS is enabled or not. */ LDAPS_ENABLED("ldapsEnabled",ADSPropertySyntax.BOOLEAN), /** Whether ADMIN is enabled or not. */ ADMIN_ENABLED("adminEnabled",ADSPropertySyntax.BOOLEAN), /** Whether StartTLS is enabled or not. */ STARTTLS_ENABLED("startTLSEnabled",ADSPropertySyntax.BOOLEAN), /** Whether JMX is enabled or not. */ JMX_ENABLED("jmxEnabled",ADSPropertySyntax.BOOLEAN), /** Whether JMX is enabled or not. */ JMXS_ENABLED("jmxsEnabled",ADSPropertySyntax.BOOLEAN), /** The location of the server. */ LOCATION("location",ADSPropertySyntax.STRING), /** The groups to which this server belongs. */ GROUPS("memberofgroups",ADSPropertySyntax.STRING), /** The unique name of the instance key public-key certificate. */ INSTANCE_KEY_ID("ds-cfg-key-id",ADSPropertySyntax.STRING), /** * The instance key-pair public-key certificate. Note: This attribute * belongs to an instance key entry, separate from the server entry and * named by the ds-cfg-key-id attribute from the server entry. */ INSTANCE_PUBLIC_KEY_CERTIFICATE("ds-cfg-public-key-certificate", ADSPropertySyntax.CERTIFICATE_BINARY); private final String attrName; private final ADSPropertySyntax attSyntax; /** * Private constructor. * * @param n * the name of the attribute. * @param s * the name of the syntax. */ private ServerProperty(String n, ADSPropertySyntax s) { attrName = n; attSyntax = s; } /** * Returns the attribute name. * * @return the attribute name. */ public String getAttributeName() { return attrName; } /** * Returns the attribute syntax. * * @return the attribute syntax. */ public ADSPropertySyntax getAttributeSyntax() { return attSyntax; } } /** Default global admin UID. */ public static final String GLOBAL_ADMIN_UID = "admin"; private static final DN ADMIN_DATA = DN.valueOf("cn=admin data"); /** The list of server properties that are multivalued. */ private static final Set MULTIVALUED_SERVER_PROPERTIES = new HashSet<>(); static { MULTIVALUED_SERVER_PROPERTIES.add(ServerProperty.GROUPS); } /** The default server group which will contain all registered servers. */ private static final String ALL_SERVERGROUP_NAME = "all-servers"; /** Enumeration containing the different server group properties that are stored in the ADS. */ private enum ServerGroupProperty { /** The UID of the server group. */ UID("cn"), /** The description of the server group. */ DESCRIPTION("description"), /** The members of the server group. */ MEMBERS("uniqueMember"); private final String attrName; /** * Private constructor. * * @param n * the attribute name. */ private ServerGroupProperty(String n) { attrName = n; } /** * Returns the attribute name. * * @return the attribute name. */ public String getAttributeName() { return attrName; } } /** The list of server group properties that are multivalued. */ private static final Set MULTIVALUED_SERVER_GROUP_PROPERTIES = new HashSet<>(); static { MULTIVALUED_SERVER_GROUP_PROPERTIES.add(ServerGroupProperty.MEMBERS); } /** The enumeration containing the different Administrator properties. */ public enum AdministratorProperty { /** The UID of the administrator. */ UID("id", ADSPropertySyntax.STRING), /** The password of the administrator. */ PASSWORD("password", ADSPropertySyntax.STRING), /** The description of the administrator. */ DESCRIPTION("description", ADSPropertySyntax.STRING), /** The DN of the administrator. */ ADMINISTRATOR_DN("administrator dn", ADSPropertySyntax.STRING), /** The administrator privilege. */ PRIVILEGE("privilege", ADSPropertySyntax.STRING); private final String attrName; private final ADSPropertySyntax attrSyntax; /** * Private constructor. * * @param n * the name of the attribute. * @param s * the name of the syntax. */ private AdministratorProperty(String n, ADSPropertySyntax s) { attrName = n; attrSyntax = s; } /** * Returns the attribute name. * * @return the attribute name. */ public String getAttributeName() { return attrName; } /** * Returns the attribute syntax. * * @return the attribute syntax. */ public ADSPropertySyntax getAttributeSyntax() { return attrSyntax; } } /** The context used to retrieve information. */ private final ConnectionWrapper connectionWrapper; /** * Constructor of the ADSContext. * * @param connectionWrapper * provide an Ldap Connection */ public ADSContext(ConnectionWrapper connectionWrapper) { this.connectionWrapper = connectionWrapper; } /** * Returns the connection used to retrieve information by this ADSContext. * * @return the connection */ public ConnectionWrapper getConnection() { return connectionWrapper; } /** * Returns the host name and port number of this connection. * * @return the hostPort of this connection */ public HostPort getHostPort() { return connectionWrapper.getHostPort(); } /** * Method called to register a server in the ADS. * * @param serverProperties * the properties of the server. * @throws ADSContextException * if the server could not be registered. */ public void registerServer(Map serverProperties) throws ADSContextException { DN dn = makeDNFromServerProperties(serverProperties); AddRequest request = newAddRequest(dn); for (Map.Entry entry : serverProperties.entrySet()) { ServerProperty prop = entry.getKey(); Attribute attribute = makeAttrFromServerProperty(prop, entry.getValue()); if (attribute != null) { request.addAttribute(attribute); } } // TODO: use another structural objectclass request.addAttribute("objectclass", "top", "ds-cfg-branch", "extensibleobject"); try { // This check is required because by default the server container entry // does not exist. if (!isExistingEntry(getServerContainerDN())) { createContainerEntry(getServerContainerDN()); } throwIfNotSuccess(connectionWrapper.getConnection().add(request)); if (serverProperties.containsKey(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE)) { registerInstanceKeyCertificate(serverProperties, dn); } // register this server into "all" groups Map serverGroupProperties = new HashMap<>(); Set memberList = getServerGroupMemberList(ALL_SERVERGROUP_NAME); if (memberList == null) { memberList = new HashSet<>(); } String newMember = "cn=" + Rdn.escapeValue(serverProperties.get(ServerProperty.ID)); memberList.add(newMember); serverGroupProperties.put(ServerGroupProperty.MEMBERS, memberList); updateServerGroup(ALL_SERVERGROUP_NAME, serverGroupProperties); // Update the server property "GROUPS" Set rawGroupList = (Set) serverProperties.get(ServerProperty.GROUPS); Set groupList = new HashSet<>(); if (rawGroupList != null) { for (Object elm : rawGroupList) { groupList.add(elm.toString()); } } groupList.add(ALL_SERVERGROUP_NAME); serverProperties.put(ServerProperty.GROUPS, groupList); updateServer(serverProperties); } catch (ADSContextException ace) { throw ace; } catch (LdapException x) { if (x.getResult().getResultCode().equals(ResultCode.ENTRY_ALREADY_EXISTS)) { throw new ADSContextException(ErrorType.ALREADY_REGISTERED); } throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Method called to update the properties of a server in the ADS. * * @param serverProperties * the new properties of the server. * @param newServerId * The new server Identifier, or null. * @throws ADSContextException * if the server could not be registered. */ private void updateServer(Map serverProperties) throws ADSContextException { DN dn = makeDNFromServerProperties(serverProperties); try { ModifyRequest request = newModifyRequest(dn); for (Map.Entry entry : serverProperties.entrySet()) { ServerProperty prop = entry.getKey(); Attribute attr = makeAttrFromServerProperty(prop, entry.getValue()); if (attr != null) { request.addModification(new Modification(REPLACE, attr)); } } throwIfNotSuccess(connectionWrapper.getConnection().modify(request)); if (serverProperties.containsKey(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE)) { registerInstanceKeyCertificate(serverProperties, dn); } } catch (ADSContextException ace) { throw ace; } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.NOT_YET_REGISTERED); } catch (Exception x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Method called to unregister a server in the ADS. Note that the server's * instance key-pair public-key certificate entry (created in * registerServer()) is left untouched. * * @param serverProperties * the properties of the server. * @throws ADSContextException * if the server could not be unregistered. */ public void unregisterServer(Map serverProperties) throws ADSContextException { DN dn = makeDNFromServerProperties(serverProperties); Connection conn = connectionWrapper.getConnection(); try { // Unregister the server from the server groups. String member = "cn=" + Rdn.escapeValue(serverProperties.get(ServerProperty.ID)); Set> serverGroups = readServerGroupRegistry(); for (Map serverGroup : serverGroups) { Set memberList = (Set) serverGroup.get(ServerGroupProperty.MEMBERS); if (memberList != null && memberList.remove(member)) { Map serverGroupProperties = new HashMap<>(); serverGroupProperties.put(ServerGroupProperty.MEMBERS, memberList); String groupName = (String) serverGroup.get(ServerGroupProperty.UID); updateServerGroup(groupName, serverGroupProperties); } } throwIfNotSuccess(conn.delete(newDeleteRequest(dn))); } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.NOT_YET_REGISTERED); } catch (LdapException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } String serverID = getServerID(serverProperties); if (serverID != null) { // Unregister the server in server groups String memberAttrName = ServerGroupProperty.MEMBERS.getAttributeName(); Filter filter = Filter.valueOf("(" + memberAttrName + "=cn=" + serverID + ")"); SearchRequest request = newSearchRequest(getServerGroupContainerDN(), SINGLE_LEVEL, filter); try (ConnectionEntryReader entryReader = conn.search(request);) { while (entryReader.hasNext()) { SearchResultEntry sr = entryReader.readEntry(); DN groupDn = sr.getName(); Attribute newAttr = new LinkedAttribute(memberAttrName); for (Attribute attr : sr.getAllAttributes()) { AttributeType attrType = attr.getAttributeDescription().getAttributeType(); if (attrType.hasName(memberAttrName)) { for (ByteString value : attr) { if (!value.toString().equalsIgnoreCase("cn=" + serverID)) { newAttr.add(value); } } } } ModificationType modType = newAttr.size() > 0 ? REPLACE : DELETE; ModifyRequest modRequest = newModifyRequest(groupDn) .addModification(new Modification(modType, newAttr)); throwIfNotSuccess(conn.modify(modRequest)); } } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.BROKEN_INSTALL); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } } /** * Returns whether a given server is already registered or not. * * @param serverProperties * the server properties. * @return true if the server was registered and * false otherwise. * @throws ADSContextException * if something went wrong. */ private boolean isServerAlreadyRegistered(Map serverProperties) throws ADSContextException { return isExistingEntry(makeDNFromServerProperties(serverProperties)); } /** * Returns whether a given administrator is already registered or not. * * @param uid * the administrator UID. * @return true if the administrator was registered and * false otherwise. * @throws ADSContextException * if something went wrong. */ private boolean isAdministratorAlreadyRegistered(String uid) throws ADSContextException { return isExistingEntry(getAdministratorDN(uid)); } /** * A convenience method that takes some server properties as parameter and if * there is no server registered associated with those properties, registers * it and if it is already registered, updates it. * * @param serverProperties * the server properties. * @return 0 if the server was registered; 1 if updated (i.e., the server * entry was already in ADS). * @throws ADSContextException * if something goes wrong. */ public int registerOrUpdateServer(Map serverProperties) throws ADSContextException { try { registerServer(serverProperties); return 0; } catch (ADSContextException x) { if (x.getError() == ErrorType.ALREADY_REGISTERED) { updateServer(serverProperties); return 1; } throw x; } } /** * Returns the member list of a group of server. * * @param serverGroupId * The group name. * @return the member list of a group of server. * @throws ADSContextException * if something goes wrong. */ private Set getServerGroupMemberList(String serverGroupId) throws ADSContextException { String dn = "cn=" + Rdn.escapeValue(serverGroupId) + "," + getServerGroupContainerDN(); SearchRequest request = newSearchRequest(dn, BASE_OBJECT, "(objectclass=*)"); try (ConnectionEntryReader entryReader = getConnection().getConnection().search(request)) { Set result = new HashSet<>(); if (!entryReader.hasNext()) { return result; } for (Attribute attr : entryReader.readEntry().getAllAttributes()) { AttributeType attrType = attr.getAttributeDescription().getAttributeType(); if (attrType.hasName(ServerGroupProperty.MEMBERS.getAttributeName())) { // We have the members list toStrings(result, attr); break; } } return result; } catch (EntryNotFoundException x) { return new HashSet<>(); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } private void toStrings(Set result, Attribute attr) { for (ByteString value : attr) { result.add(value.toString()); } } /** * Returns a set containing the servers that are registered in the ADS. * * @return a set containing the servers that are registered in the ADS. * @throws ADSContextException * if something goes wrong. */ public Set> readServerRegistry() throws ADSContextException { Set> result = new HashSet<>(); SearchRequest request = newSearchRequest(getServerContainerDN(), SINGLE_LEVEL, objectClassPresent()); try (ConnectionEntryReader entryReader = connectionWrapper.getConnection().search(request)) { while (entryReader.hasNext()) { SearchResultEntry sr = entryReader.readEntry(); Map properties = makePropertiesFromServerAttrs(sr); Object keyId = properties.get(ServerProperty.INSTANCE_KEY_ID); if (keyId != null) { SearchRequest request2 = newSearchRequest( getInstanceKeysContainerDN(), SINGLE_LEVEL, Filter.valueOf("(ds-cfg-key-id=" + keyId + ")"), "ds-cfg-public-key-certificate;binary"); try (ConnectionEntryReader entryReader2 = connectionWrapper.getConnection().search(request2)) { boolean found = false; while (entryReader2.hasNext()) { SearchResultEntry certEntry = entryReader2.readEntry(); Attribute certAttr = certEntry.getAttribute("ds-cfg-public-key-certificate;binary"); properties.put(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE, certAttr.firstValue().toByteArray()); found = true; } if (!found) { logger.warn(LocalizableMessage.raw("Could not find public key for " + properties)); } } catch (EntryNotFoundException x) { logger.warn(LocalizableMessage.raw("Could not find public key for " + properties)); } } result.add(properties); } return result; } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.BROKEN_INSTALL); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Creates a Server Group in the ADS. * * @param serverGroupProperties * the properties of the server group to be created. * @throws ADSContextException * if something goes wrong. */ private void createServerGroup(Map serverGroupProperties) throws ADSContextException { String dn = makeDNFromServerGroupProperties(serverGroupProperties); AddRequest request = newAddRequest(dn) .addAttribute("objectclass", "top", "groupOfUniqueNames"); for (Map.Entry entry : serverGroupProperties.entrySet()) { ServerGroupProperty prop = entry.getKey(); request.addAttribute(makeAttrFromServerGroupProperty(prop, entry.getValue())); } try { throwIfNotSuccess(connectionWrapper.getConnection().add(request)); } catch (LdapException x) { if (x.getResult().getResultCode().equals(ResultCode.ENTRY_ALREADY_EXISTS)) { throw new ADSContextException(ErrorType.ALREADY_REGISTERED); } throw new ADSContextException(ErrorType.BROKEN_INSTALL, x); } } /** * Updates the properties of a Server Group in the ADS. * * @param serverGroupProperties * the new properties of the server group to be updated. * @param groupID * The group name. * @throws ADSContextException * if something goes wrong. */ private void updateServerGroup(String groupID, Map serverGroupProperties) throws ADSContextException { String dn = "cn=" + Rdn.escapeValue(groupID) + "," + getServerGroupContainerDN(); try { // Entry renaming ? if (serverGroupProperties.containsKey(ServerGroupProperty.UID)) { String newGroupId = serverGroupProperties.get(ServerGroupProperty.UID).toString(); if (!newGroupId.equals(groupID)) { // Rename to entry String newDN = ("cn=" + Rdn.escapeValue(newGroupId) + "," + getServerGroupContainerDN()); throwIfNotSuccess(connectionWrapper.getConnection().modifyDN(dn, newDN)); dn = newDN; } // In any case, we remove the "cn" attribute. serverGroupProperties.remove(ServerGroupProperty.UID); } if (serverGroupProperties.isEmpty()) { return; } // Transform 'properties' into 'attributes' ModifyRequest request = newModifyRequest(dn); for (Map.Entry entry : serverGroupProperties.entrySet()) { ServerGroupProperty prop = entry.getKey(); request.addModification(new Modification( REPLACE, makeAttrFromServerGroupProperty(prop, entry.getValue()))); } throwIfNotSuccess(connectionWrapper.getConnection().modify(request)); } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.NOT_YET_REGISTERED); } catch (LdapException x) { if (x.getResult().getResultCode().equals(ResultCode.ENTRY_ALREADY_EXISTS)) { throw new ADSContextException(ErrorType.ALREADY_REGISTERED); } throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Returns a set containing the server groups that are defined in the ADS. * * @return a set containing the server groups that are defined in the ADS. * @throws ADSContextException * if something goes wrong. */ private Set> readServerGroupRegistry() throws ADSContextException { SearchRequest request = newSearchRequest(getServerGroupContainerDN(), SINGLE_LEVEL, objectClassPresent()); try (ConnectionEntryReader entryReader = connectionWrapper.getConnection().search(request)) { Set> result = new HashSet<>(); while (entryReader.hasNext()) { SearchResultEntry sr = entryReader.readEntry(); result.add(makePropertiesFromServerGroupAttrs(sr)); } return result; } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.BROKEN_INSTALL); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Returns a set containing the administrators that are defined in the ADS. * * @return a set containing the administrators that are defined in the ADS. * @throws ADSContextException * if something goes wrong. */ public Set> readAdministratorRegistry() throws ADSContextException { Set> result = new HashSet<>(); SearchRequest request = newSearchRequest( getAdministratorContainerDN(), SINGLE_LEVEL, objectClassPresent(), "cn", "userpassword", "ds-privilege-name", "description"); try (ConnectionEntryReader entryReader = connectionWrapper.getConnection().search(request)) { while (entryReader.hasNext()) { SearchResultEntry sr = entryReader.readEntry(); result.add(makePropertiesFromAdministratorAttrs(sr.getName().rdn(), sr.getAllAttributes())); } } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.BROKEN_INSTALL); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } return result; } /** * Creates the Administration Data in the server. The call to this method * assumes that OpenDJ.jar has already been loaded. * * @param backendName * the backend name which will handle admin information. * null to use the default backend name for the admin * information. * @throws ADSContextException * if something goes wrong. */ public void createAdminData(String backendName) throws ADSContextException { // Add the administration suffix createAdministrationSuffix(backendName); createAdminDataContainers(); } /** Create container entries. */ private void createAdminDataContainers() throws ADSContextException { // Create the DIT below the administration suffix if (!isExistingEntry(getAdministrationSuffixDN())) { createTopContainerEntry(); } if (!isExistingEntry(getAdministratorContainerDN())) { createAdministratorContainerEntry(); } if (!isExistingEntry(getServerContainerDN())) { createContainerEntry(getServerContainerDN()); } if (!isExistingEntry(getServerGroupContainerDN())) { createContainerEntry(getServerGroupContainerDN()); } // Add the default "all-servers" group if (!isExistingEntry((getAllServerGroupDN()))) { Map allServersGroupsMap = new HashMap<>(); allServersGroupsMap.put(ServerGroupProperty.UID, ALL_SERVERGROUP_NAME); createServerGroup(allServersGroupsMap); } // Create the CryptoManager instance key DIT below the administration suffix if (!isExistingEntry(getInstanceKeysContainerDN())) { createContainerEntry(getInstanceKeysContainerDN()); } // Create the CryptoManager secret key DIT below the administration suffix if (!isExistingEntry(getSecretKeysContainerDN())) { createContainerEntry(getSecretKeysContainerDN()); } } /** * Removes the administration data. * * @param removeAdministrators * {@code true} if administrators should be removed. It may not be * possible to remove administrators if the operation is being * performed by one of the administrators because it will cause the * administrator to be disconnected. * @throws ADSContextException * if something goes wrong. */ public void removeAdminData(boolean removeAdministrators) throws ADSContextException { DN[] dns = { getServerContainerDN(), getServerGroupContainerDN(), removeAdministrators ? getAdministratorContainerDN() : null }; try { for (DN dn : dns) { if (dn != null) { if (isExistingEntry(dn)) { DeleteRequest request = newDeleteRequest(dn) .addControl(SubtreeDeleteRequestControl.newControl(true)); connectionWrapper.getConnection().delete(request); } } } // Recreate the container entries: createAdminDataContainers(); } catch (LdapException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Returns true if the server contains Administration Data and * false otherwise. * * @return true if the server contains Administration Data and * false otherwise. * @throws ADSContextException * if something goes wrong. */ public boolean hasAdminData() throws ADSContextException { DN[] dns = { getAdministratorContainerDN(), getAllServerGroupDN(), getServerContainerDN(), getInstanceKeysContainerDN(), getSecretKeysContainerDN() }; boolean hasAdminData = true; for (int i = 0; i < dns.length && hasAdminData; i++) { hasAdminData = isExistingEntry(dns[i]); } return hasAdminData; } /** * Returns the DN of the administrator for a given UID. * * @param uid * the UID to be used to generate the DN. * @return the DN of the administrator for the given UID: */ public static DN getAdministratorDN(String uid) { return DN.valueOf("cn=" + Rdn.escapeValue(uid) + "," + getAdministratorContainerDN()); } /** * Creates an Administrator in the ADS. * * @param adminProperties * the properties of the administrator to be created. * @throws ADSContextException * if something goes wrong. */ public void createAdministrator(Map adminProperties) throws ADSContextException { AddRequest request = newAddRequest(getAdministratorDN(getAdministratorUID(adminProperties))); addAttrsFromAdministratorProperties(request, adminProperties, true); try { throwIfNotSuccess(connectionWrapper.getConnection().add(request)); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (LdapException x) { if (x.getResult().getResultCode().equals(ResultCode.ENTRY_ALREADY_EXISTS)) { throw new ADSContextException(ErrorType.ALREADY_REGISTERED); } throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Deletes the administrator in the ADS. * * @param adminProperties * the properties of the administrator to be deleted. * @throws ADSContextException * if something goes wrong. */ public void deleteAdministrator(Map adminProperties) throws ADSContextException { DN dnCentralAdmin = getAdministratorDN(getAdministratorUID(adminProperties)); try { throwIfNotSuccess(connectionWrapper.getConnection().delete(newDeleteRequest(dnCentralAdmin))); } catch (EntryNotFoundException x) { throw new ADSContextException(ErrorType.NOT_YET_REGISTERED); } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (LdapException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Returns the DN of the suffix that contains the administration data. * * @return the DN of the suffix that contains the administration data. */ public static DN getAdministrationSuffixDN() { return ADMIN_DATA; } /** * This method returns the DN of the entry that corresponds to the given host * name and installation path. * * @param hostname * the host name. * @param ipath * the installation path. * @return the DN of the entry that corresponds to the given host name and * installation path. * @throws ADSContextException * if something goes wrong. */ private static DN makeDNFromHostnameAndPath(String hostname, String ipath) throws ADSContextException { return DN.valueOf("cn=" + Rdn.escapeValue(hostname + "@" + ipath) + "," + getServerContainerDN()); } /** * This method returns the DN of the entry that corresponds to the given host * name port representation. * * @param serverUniqueId * the host name and port. * @return the DN of the entry that corresponds to the given host name and * port. * @throws ADSContextException * if something goes wrong. */ private static DN makeDNFromServerUniqueId(String serverUniqueId) throws ADSContextException { return DN.valueOf("cn=" + Rdn.escapeValue(serverUniqueId) + "," + getServerContainerDN()); } /** * This method returns the DN of the entry that corresponds to the given * server group properties. * * @param serverGroupProperties * the server group properties * @return the DN of the entry that corresponds to the given server group * properties. * @throws ADSContextException * if something goes wrong. */ private static String makeDNFromServerGroupProperties(Map serverGroupProperties) throws ADSContextException { String serverGroupId = (String) serverGroupProperties.get(ServerGroupProperty.UID); if (serverGroupId == null) { throw new ADSContextException(ErrorType.MISSING_NAME); } return "cn=" + Rdn.escapeValue(serverGroupId) + "," + getServerGroupContainerDN(); } /** * This method returns the DN of the entry that corresponds to the given * server properties. * * @param serverProperties * the server properties. * @return the DN of the entry that corresponds to the given server * properties. * @throws ADSContextException * if something goes wrong. */ private static DN makeDNFromServerProperties(Map serverProperties) throws ADSContextException { String serverID = getServerID(serverProperties); if (serverID != null) { return makeDNFromServerUniqueId(serverID); } String hostname = getHostname(serverProperties); try { String ipath = getInstallPath(serverProperties); return makeDNFromHostnameAndPath(hostname, ipath); } catch (ADSContextException ace) { ServerDescriptor s = ServerDescriptor.createStandalone(serverProperties); return makeDNFromServerUniqueId(s.getHostPort(true).toString()); } } /** * Enrich the provided add request with the attributes for some administrator properties. * * @param adminProperties * the administrator properties. * @param passwordRequired * Indicates if the properties should include the password. * @throws ADSContextException * if something goes wrong. */ private static void addAttrsFromAdministratorProperties(AddRequest request, Map adminProperties, boolean passwordRequired) throws ADSContextException { if (passwordRequired) { request.addAttribute("userPassword", getAdministratorPassword(adminProperties)); } request.addAttribute("objectclass", "top", "person"); request.addAttribute("sn", GLOBAL_ADMIN_UID); if (adminProperties.containsKey(AdministratorProperty.DESCRIPTION)) { request.addAttribute("description", adminProperties.get(AdministratorProperty.DESCRIPTION)); } if (adminProperties.containsKey(AdministratorProperty.PRIVILEGE)) { LinkedList privileges = (LinkedList) adminProperties.get(AdministratorProperty.PRIVILEGE); for (Object o : privileges) { String p = o.toString(); if (p.startsWith("-")) { request.removeAttribute("ds-privilege-name", p.substring(1)); } else { request.addAttribute("ds-privilege-name", p); } } } else { request.addAttribute(addRootPrivileges()); } // Add the RootDNs Password policy so the password do not expire. request.addAttribute("ds-pwp-password-policy-dn", "cn=Root Password Policy,cn=Password Policies,cn=config"); } /** * Builds an attribute which contains 'root' privileges. * * @return The attribute which contains 'root' privileges. */ private static Attribute addRootPrivileges() { Attribute privilege = new LinkedAttribute("ds-privilege-name"); privilege.add("bypass-acl"); privilege.add("modify-acl"); privilege.add("config-read"); privilege.add("config-write"); privilege.add("ldif-import"); privilege.add("ldif-export"); privilege.add("backend-backup"); privilege.add("backend-restore"); privilege.add("server-shutdown"); privilege.add("server-restart"); privilege.add("disconnect-client"); privilege.add("cancel-request"); privilege.add("password-reset"); privilege.add("update-schema"); privilege.add("privilege-change"); privilege.add("unindexed-search"); privilege.add("subentry-write"); privilege.add("changelog-read"); return privilege; } /** * Returns the attribute for a given server property. * * @param property * the server property. * @param value * the value. * @return the attribute for a given server property. */ private static Attribute makeAttrFromServerProperty(ServerProperty property, Object value) { switch (property) { case INSTANCE_PUBLIC_KEY_CERTIFICATE: // used in separate instance key entry return null; case GROUPS: return new LinkedAttribute(ServerProperty.GROUPS.getAttributeName(), ((Collection) value)); default: return new LinkedAttribute(property.getAttributeName(), value); } } /** * Returns the attribute for a given server group property. * * @param property * the server group property. * @param value * the value. * @return the attribute for a given server group property. */ private static Attribute makeAttrFromServerGroupProperty(ServerGroupProperty property, Object value) { switch (property) { case MEMBERS: return new LinkedAttribute(ServerGroupProperty.MEMBERS.getAttributeName(), (Collection) value); default: return new LinkedAttribute(property.getAttributeName(), value); } } /** * Returns the properties of a server group for some LDAP attributes. * * @param entry * the LDAP entry. * @return the properties of a server group for some LDAP attributes. * @throws ADSContextException * if something goes wrong. */ private Map makePropertiesFromServerGroupAttrs(SearchResultEntry entry) throws ADSContextException { Map result = new HashMap<>(); for (ServerGroupProperty prop : ServerGroupProperty.values()) { Attribute attr = entry.getAttribute(prop.getAttributeName()); if (attr == null) { continue; } Object value; if (attr.size() >= 1 && MULTIVALUED_SERVER_GROUP_PROPERTIES.contains(prop)) { Set set = new HashSet<>(); toStrings(set, attr); value = set; } else { value = attr.firstValueAsString(); } result.put(prop, value); } return result; } /** * Returns the properties of a server for some LDAP attributes. * * @param entry * the entry. * @return the properties of a server for some LDAP attributes. * @throws ADSContextException * if something goes wrong. */ private Map makePropertiesFromServerAttrs(SearchResultEntry entry) throws ADSContextException { Map result = new HashMap<>(); for (Attribute attr : entry.getAllAttributes()) { AttributeType attrType = attr.getAttributeDescription().getAttributeType(); Object value; ServerProperty prop = null; ServerProperty[] props = ServerProperty.values(); for (int i = 0; i < props.length && prop == null; i++) { String v = props[i].getAttributeName(); if (attrType.hasName(v)) { prop = props[i]; } } if (prop == null) { // Do not handle it } else { if (attr.size() >= 1 && MULTIVALUED_SERVER_PROPERTIES.contains(prop)) { Set set = new HashSet<>(); toStrings(set, attr); value = set; } else { value = attr.firstValueAsString(); } result.put(prop, value); } } return result; } /** * Returns the properties of an administrator for some rdn and LDAP * attributes. * * @param rdn * the RDN. * @param attrs * the LDAP attributes. * @return the properties of an administrator for the given rdn and LDAP * attributes. * @throws ADSContextException * if something goes wrong. */ private Map makePropertiesFromAdministratorAttrs(RDN rdn, Iterable attrs) { Map result = new HashMap<>(); result.put(AdministratorProperty.ADMINISTRATOR_DN, rdn + "," + getAdministratorContainerDN()); for (Attribute attr : attrs) { AttributeType attrName = attr.getAttributeDescription().getAttributeType(); if (attrName.hasName("cn")) { result.put(AdministratorProperty.UID, attr.firstValueAsString()); } else if (attrName.hasName("userpassword")) { result.put(AdministratorProperty.PASSWORD, new String(attr.firstValue().toByteArray())); } else if (attrName.hasName("description")) { result.put(AdministratorProperty.DESCRIPTION, attr.firstValueAsString()); } else if (attrName.hasName("ds-privilege-name")) { LinkedHashSet privileges = new LinkedHashSet<>(); toStrings(privileges, attr); result.put(AdministratorProperty.PRIVILEGE, privileges); } } return result; } /** * Returns the parent entry of the server entries. * * @return the parent entry of the server entries. */ private static DN getServerContainerDN() { return DN.valueOf("cn=Servers," + getAdministrationSuffixDN()); } /** * Returns the parent entry of the administrator entries. * * @return the parent entry of the administrator entries. */ public static DN getAdministratorContainerDN() { return DN.valueOf("cn=Administrators," + getAdministrationSuffixDN()); } /** * Returns the parent entry of the server group entries. * * @return the parent entry of the server group entries. */ private static DN getServerGroupContainerDN() { return DN.valueOf("cn=Server Groups," + getAdministrationSuffixDN()); } /** * Returns the all server group entry DN. * * @return the all server group entry DN. */ private static DN getAllServerGroupDN() { return DN.valueOf("cn=" + Rdn.escapeValue(ALL_SERVERGROUP_NAME) + "," + getServerGroupContainerDN()); } /** * Returns the host name for the given properties. * * @param serverProperties * the server properties. * @return the host name for the given properties. * @throws ADSContextException * if the host name could not be found or its value is not valid. */ private static String getHostname(Map serverProperties) throws ADSContextException { String result = (String) serverProperties.get(ServerProperty.HOST_NAME); if (result == null) { throw new ADSContextException(ErrorType.MISSING_HOSTNAME); } else if (result.length() == 0) { throw new ADSContextException(ErrorType.NOVALID_HOSTNAME); } return result; } /** * Returns the Server ID for the given properties. * * @param serverProperties * the server properties. * @return the server ID for the given properties or null. */ private static String getServerID(Map serverProperties) { String result = (String) serverProperties.get(ServerProperty.ID); if (result != null && result.length() == 0) { result = null; } return result; } /** * Returns the install path for the given properties. * * @param serverProperties * the server properties. * @return the install path for the given properties. * @throws ADSContextException * if the install path could not be found or its value is not valid. */ private static String getInstallPath(Map serverProperties) throws ADSContextException { String result = (String) serverProperties.get(ServerProperty.INSTANCE_PATH); if (result == null) { throw new ADSContextException(ErrorType.MISSING_IPATH); } else if (result.length() == 0) { throw new ADSContextException(ErrorType.NOVALID_IPATH); } return result; } /** * Returns the Administrator UID for the given properties. * * @param adminProperties * the server properties. * @return the Administrator UID for the given properties. * @throws ADSContextException * if the administrator UID could not be found. */ private static String getAdministratorUID(Map adminProperties) throws ADSContextException { String result = (String) adminProperties.get(AdministratorProperty.UID); if (result == null) { throw new ADSContextException(ErrorType.MISSING_ADMIN_UID); } return result; } /** * Returns the Administrator password for the given properties. * * @param adminProperties * the server properties. * @return the Administrator password for the given properties. * @throws ADSContextException * if the administrator password could not be found. */ private static String getAdministratorPassword(Map adminProperties) throws ADSContextException { String result = (String) adminProperties.get(AdministratorProperty.PASSWORD); if (result == null) { throw new ADSContextException(ErrorType.MISSING_ADMIN_PASSWORD); } return result; } // LDAP utilities /** * Tells whether an entry with the provided DN exists. * * @param dn * the DN to check. * @return true if the entry exists and false if it * does not. * @throws ADSContextException * if an error occurred while checking if the entry exists or not. */ private boolean isExistingEntry(DN dn) throws ADSContextException { SearchRequest request = newSearchRequest(dn, BASE_OBJECT, objectClassPresent(), NO_ATTRIBUTES); try (ConnectionEntryReader entryReader = getConnection().getConnection().search(request)) { while (entryReader.hasNext()) { entryReader.readEntry(); return true; } return false; } catch (EntryNotFoundException x) { return false; } catch (AuthorizationException x) { throw new ADSContextException(ErrorType.ACCESS_PERMISSION); } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Creates a container entry with the given dn. * * @param dn * the entry of the new entry to be created. * @throws ADSContextException * if the entry could not be created. */ private void createContainerEntry(DN dn) throws ADSContextException { createEntry(newAddRequest(dn).addAttribute("objectclass", "top", "ds-cfg-branch")); } /** * Creates the administrator container entry. * * @throws ADSContextException * if the entry could not be created. */ private void createAdministratorContainerEntry() throws ADSContextException { AddRequest request = newAddRequest(getAdministratorContainerDN()) .addAttribute("objectclass", "groupofurls") .addAttribute("memberURL", "ldap:///" + getAdministratorContainerDN() + "??one?(objectclass=*)") .addAttribute("description", "Group of identities which have full access."); createEntry(request); } /** * Creates the top container entry. * * @throws ADSContextException * if the entry could not be created. */ private void createTopContainerEntry() throws ADSContextException { AddRequest request = newAddRequest(getAdministrationSuffixDN()) .addAttribute("objectclass", "top", "ds-cfg-branch"); createEntry(request); } /** * Creates an entry with the provided add request. * * @param addRequest * the add request. * @throws ADSContextException * if the entry could not be created. */ private void createEntry(AddRequest request) throws ADSContextException { try { throwIfNotSuccess(getConnection().getConnection().add(request)); } catch (LdapException e) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, e); } } private void throwIfNotSuccess(Result result) throws LdapException { ResultCode rc = result.getResultCode(); if (rc.isExceptional()) { throw LdapException.newLdapException(result); } } /** * Creates the Administration Suffix. * * @param backendName * the backend name to be used for the Administration Suffix. If this * value is null the default backendName for the Administration * Suffix will be used. * @throws ADSContextException * if something goes wrong. */ private void createAdministrationSuffix(String backendName) throws ADSContextException { ADSContextHelper helper = new ADSContextHelper(); String ben = backendName; if (backendName == null) { ben = getDefaultBackendName(); } helper.createAdministrationSuffix(connectionWrapper, ben); } /** * Returns the default backend name of the administration data. * * @return the default backend name of the administration data. */ public static String getDefaultBackendName() { return "adminRoot"; } /** * Returns the LDIF file of the administration data. * * @return the LDIF file of the administration data. */ static String getAdminLDIFFile() { return "config" + File.separator + "admin-backend.ldif"; } /** CryptoManager related types, fields, and methods. */ /** * Returns the parent entry of the server key entries in ADS. * * @return the parent entry of the server key entries in ADS. */ static DN getInstanceKeysContainerDN() { return DN.valueOf("cn=instance keys," + getAdministrationSuffixDN()); } /** * Returns the parent entry of the secret key entries in ADS. * * @return the parent entry of the secret key entries in ADS. */ private static DN getSecretKeysContainerDN() { return DN.valueOf("cn=secret keys," + getAdministrationSuffixDN()); } /** * Tells whether the provided server is registered in the registry. * * @param server * the server. * @param registry * the registry. * @return true if the server is registered in the registry and * false otherwise. */ public static boolean isRegistered(ServerDescriptor server, Set> registry) { for (Map s : registry) { ServerDescriptor servInRegistry = ServerDescriptor.createStandalone(s); if (servInRegistry.getId().equals(server.getId())) { return true; } } return false; } /** * Register instance key-pair public-key certificate provided in * serverProperties: generate a key-id attribute if one is not provided (as * expected); add an instance key public-key certificate entry for the key * certificate; and associate the certificate entry with the server entry via * the key ID attribute. * * @param serverProperties * Properties of the server being registered to which the instance * key entry belongs. * @param serverEntryDn * The server's ADS entry DN. * @throws ADSContextException * In case there is a problem registering the instance public key certificate ID */ private void registerInstanceKeyCertificate(Map serverProperties, DN serverEntryDn) throws ADSContextException { ADSContextHelper helper = new ADSContextHelper(); helper.registerInstanceKeyCertificate(connectionWrapper, serverProperties, serverEntryDn); } /** * Return the set of valid (i.e., not tagged as compromised) instance key-pair * public-key certificate entries in ADS. NOTE: calling this method assumes * that all the jar files are present in the classpath. * * @return The set of valid (i.e., not tagged as compromised) instance * key-pair public-key certificate entries in ADS represented as a Map * from ds-cfg-key-id value to ds-cfg-public-key-certificate;binary * value. Note that the collection might be empty. * @throws ADSContextException * in case of problems with the entry search. * @see org.opends.server.crypto.CryptoManagerImpl#getTrustedCertificates */ public Map getTrustedCertificates() throws ADSContextException { final DN baseDN = getInstanceKeysContainerDN(); ADSContextHelper helper = new ADSContextHelper(); final String FILTER_OC_INSTANCE_KEY = "(objectclass=" + helper.getOcCryptoInstanceKey() + ")"; final String FILTER_NOT_COMPROMISED = "(!(" + helper.getAttrCryptoKeyCompromisedTime() + "=*))"; final Filter searchFilter = Filter.valueOf("(&" + FILTER_OC_INSTANCE_KEY + FILTER_NOT_COMPROMISED + ")"); String instanceKeyId = ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName(); String instanceKeyCertificate = ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE.getAttributeName() + ";binary"; SearchRequest request = newSearchRequest(baseDN, SINGLE_LEVEL, searchFilter, instanceKeyId, instanceKeyCertificate); final Map certificateMap = new HashMap<>(); try (ConnectionEntryReader entryReader = connectionWrapper.getConnection().search(request)) { while (entryReader.hasNext()) { final SearchResultEntry entry = entryReader.readEntry(); final Attribute keyIDAttr = entry.getAttribute(instanceKeyId); final Attribute keyCertAttr = entry.getAttribute(instanceKeyCertificate); if (null == keyIDAttr || null == keyCertAttr) { continue;// schema viol. } certificateMap.put(keyIDAttr.firstValueAsString(), keyCertAttr.firstValue().toByteArray()); } return certificateMap; } catch (IOException x) { throw new ADSContextException(ErrorType.ERROR_UNEXPECTED, x); } } /** * Merge the contents of this ADSContext with the contents of the provided * ADSContext. Note that only the contents of this ADSContext will be updated. * * @param adsCtx * the other ADSContext to merge the contents with. * @throws ADSContextException * if there was an error during the merge. */ public void mergeWithRegistry(ADSContext adsCtx) throws ADSContextException { try { mergeAdministrators(adsCtx); mergeServerGroups(adsCtx); mergeServers(adsCtx); } catch (ADSContextException adce) { LocalizableMessage msg = ERR_ADS_MERGE.get(getHostPort(), adsCtx.getHostPort(), adce.getMessageObject()); throw new ADSContextException(ErrorType.ERROR_MERGING, msg, adce); } } /** * Merge the administrator contents of this ADSContext with the contents of * the provided ADSContext. Note that only the contents of this ADSContext * will be updated. * * @param adsCtx * the other ADSContext to merge the contents with. * @throws ADSContextException * if there was an error during the merge. */ private void mergeAdministrators(ADSContext adsCtx) throws ADSContextException { Set> admins2 = adsCtx.readAdministratorRegistry(); SortedSet notDefinedAdmins = new TreeSet<>(); for (Map admin2 : admins2) { String uid = (String) admin2.get(AdministratorProperty.UID); if (!isAdministratorAlreadyRegistered(uid)) { notDefinedAdmins.add(uid); } } if (!notDefinedAdmins.isEmpty()) { LocalizableMessage msg = ERR_ADS_ADMINISTRATOR_MERGE.get( adsCtx.getHostPort(), getHostPort(), joinAsString(Constants.LINE_SEPARATOR, notDefinedAdmins), getHostPort()); throw new ADSContextException(ErrorType.ERROR_MERGING, msg, null); } } /** * Merge the groups contents of this ADSContext with the contents of the * provided ADSContext. Note that only the contents of this ADSContext will be * updated. * * @param adsCtx * the other ADSContext to merge the contents with. * @throws ADSContextException * if there was an error during the merge. */ private void mergeServerGroups(ADSContext adsCtx) throws ADSContextException { Set> serverGroups1 = readServerGroupRegistry(); Set> serverGroups2 = adsCtx.readServerGroupRegistry(); for (Map group2 : serverGroups2) { Map group1 = null; String uid2 = (String) group2.get(ServerGroupProperty.UID); for (Map gr : serverGroups1) { String uid1 = (String) gr.get(ServerGroupProperty.UID); if (uid1.equalsIgnoreCase(uid2)) { group1 = gr; break; } } if (group1 != null) { // Merge the members, keep the description on this ADS. Set member1List = getServerGroupMemberList(uid2); if (member1List == null) { member1List = new HashSet<>(); } Set member2List = adsCtx.getServerGroupMemberList(uid2); if (member2List != null && !member2List.isEmpty()) { member1List.addAll(member2List); Map newProperties = new HashMap<>(); newProperties.put(ServerGroupProperty.MEMBERS, member1List); updateServerGroup(uid2, newProperties); } } else { createServerGroup(group2); } } } /** * Merge the server contents of this ADSContext with the contents of the * provided ADSContext. Note that only the contents of this ADSContext will be * updated. * * @param adsCtx * the other ADSContext to merge the contents with. * @throws ADSContextException * if there was an error during the merge. */ private void mergeServers(ADSContext adsCtx) throws ADSContextException { for (Map server2 : adsCtx.readServerRegistry()) { if (!isServerAlreadyRegistered(server2)) { registerServer(server2); } } } @Override public String toString() { return getClass().getSimpleName() + "(" + connectionWrapper + ")"; } }