/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.backends; import static org.forgerock.util.Reject.*; import static org.opends.messages.BackendMessages.*; import static org.opends.messages.ConfigMessages.*; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.util.CollectionUtils.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.ConditionResult; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.util.Reject; import org.forgerock.util.Utils; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.RootDSEBackendCfg; import org.opends.server.api.Backend; import org.opends.server.api.ClientConnection; import org.opends.server.config.ConfigEntry; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyDNOperation; import org.opends.server.core.ModifyOperation; import org.opends.server.core.SearchOperation; import org.opends.server.core.ServerContext; import org.opends.server.types.*; import org.opends.server.util.BuildVersion; import org.opends.server.util.LDIFWriter; /** * This class defines a backend to hold the Directory Server root DSE. It is a * kind of meta-backend in that it will dynamically generate the root DSE entry * (although there will be some caching) for base-level searches, and will * simply redirect to other backends for operations in other scopes. * <BR><BR> * This should not be treated like a regular backend when it comes to * initializing the server configuration. It should only be initialized after * all other backends are configured. As such, it should have a special entry * in the configuration rather than being placed under the cn=Backends branch * with the other backends. */ public class RootDSEBackend extends Backend<RootDSEBackendCfg> implements ConfigurationChangeListener<RootDSEBackendCfg> { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * The set of standard "static" attributes that we will always include in the * root DSE entry and won't change while the server is running. */ private List<Attribute> staticDSEAttributes; /** The set of user-defined attributes that will be included in the root DSE entry. */ private List<Attribute> userDefinedAttributes; /** * Indicates whether the attributes of the root DSE should always be treated * as user attributes even if they are defined as operational in the schema. */ private boolean showAllAttributes; /** The set of objectclasses that will be used in the root DSE entry. */ private Map<ObjectClass, String> dseObjectClasses; /** The current configuration state. */ private RootDSEBackendCfg currentConfig; /** The DN of the configuration entry for this backend. */ private DN configEntryDN; /** The DN for the root DSE. */ private DN rootDSEDN; /** The set of base DNs for this backend. */ private DN[] baseDNs; /** * The set of subordinate base DNs and their associated backends that will be * used for non-base searches. */ private ConcurrentHashMap<DN, Backend<?>> subordinateBaseDNs; /** * Creates a new backend with the provided information. All backend * implementations must implement a default constructor that use * <CODE>super()</CODE> to invoke this constructor. */ public RootDSEBackend() { super(); // Perform all initialization in initializeBackend. } @Override public void configureBackend(RootDSEBackendCfg config, ServerContext serverContext) throws ConfigException { Reject.ifNull(config); currentConfig = config; configEntryDN = config.dn(); } @Override public void openBackend() throws ConfigException, InitializationException { ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN); // Make sure that a configuration entry was provided. If not, then we will // not be able to complete initialization. if (configEntry == null) { LocalizableMessage message = ERR_ROOTDSE_CONFIG_ENTRY_NULL.get(); throw new ConfigException(message); } userDefinedAttributes = new ArrayList<>(); addAllUserDefinedAttrs(userDefinedAttributes, configEntry.getEntry()); // Create the set of base DNs that we will handle. In this case, it's just // the root DSE. rootDSEDN = DN.rootDN(); this.baseDNs = new DN[] { rootDSEDN }; // Create the set of subordinate base DNs. If this is specified in the // configuration, then use that set. Otherwise, use the set of non-private // backends defined in the server. try { Set<DN> subDNs = currentConfig.getSubordinateBaseDN(); if (subDNs.isEmpty()) { // This is fine -- we'll just use the set of user-defined suffixes. subordinateBaseDNs = null; } else { subordinateBaseDNs = new ConcurrentHashMap<>(); for (DN baseDN : subDNs) { Backend<?> backend = DirectoryServer.getBackend(baseDN); if (backend == null) { logger.warn(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE, baseDN); } else { subordinateBaseDNs.put(baseDN, backend); } } } } catch (Exception e) { logger.traceException(e); LocalizableMessage message = WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get( stackTraceToSingleLineString(e)); throw new InitializationException(message, e); } // Determine whether all root DSE attributes should be treated as user // attributes. showAllAttributes = currentConfig.isShowAllAttributes(); // Construct the set of "static" attributes that will always be present in // the root DSE. staticDSEAttributes = new ArrayList<>(); staticDSEAttributes.add(Attributes.create(ATTR_VENDOR_NAME, SERVER_VENDOR_NAME)); staticDSEAttributes.add(Attributes.create(ATTR_VENDOR_VERSION, DirectoryServer.getVersionString())); staticDSEAttributes.add(Attributes.create("fullVendorVersion", BuildVersion.binaryVersion().toString())); // Construct the set of objectclasses to include in the root DSE entry. dseObjectClasses = new HashMap<>(2); ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP); if (topOC == null) { topOC = DirectoryServer.getDefaultObjectClass(OC_TOP); } dseObjectClasses.put(topOC, OC_TOP); ObjectClass rootDSEOC = DirectoryServer.getObjectClass(OC_ROOT_DSE); if (rootDSEOC == null) { rootDSEOC = DirectoryServer.getDefaultObjectClass(OC_ROOT_DSE); } dseObjectClasses.put(rootDSEOC, OC_ROOT_DSE); // Set the backend ID for this backend. The identifier needs to be // specific enough to avoid conflict with user backend identifiers. setBackendID("__root.dse__"); // Register as a change listener. currentConfig.addChangeListener(this); } /** * Get the set of user-defined attributes for the configuration entry. Any * attributes that we do not recognize will be included directly in the root DSE. */ private void addAllUserDefinedAttrs(List<Attribute> userDefinedAttrs, Entry configEntry) { for (List<Attribute> attrs : configEntry.getUserAttributes().values()) { for (Attribute a : attrs) { if (!isDSEConfigAttribute(a)) { userDefinedAttrs.add(a); } } } for (List<Attribute> attrs : configEntry.getOperationalAttributes().values()) { for (Attribute a : attrs) { if (!isDSEConfigAttribute(a)) { userDefinedAttrs.add(a); } } } } @Override public void closeBackend() { currentConfig.removeChangeListener(this); } /** * Indicates whether the provided attribute is one that is used in the * configuration of this backend. * * @param attribute The attribute for which to make the determination. * * @return <CODE>true</CODE> if the provided attribute is one that is used in * the configuration of this backend, <CODE>false</CODE> if not. */ private boolean isDSEConfigAttribute(Attribute attribute) { AttributeType attrType = attribute.getAttributeType(); return attrType.hasName(ATTR_ROOT_DSE_SUBORDINATE_BASE_DN.toLowerCase()) || attrType.hasName(ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES.toLowerCase()) || attrType.hasName(ATTR_COMMON_NAME); } @Override public DN[] getBaseDNs() { return baseDNs; } @Override public synchronized long getEntryCount() { // There is always just a single entry in this backend. return 1; } @Override public boolean isIndexed(AttributeType attributeType, IndexType indexType) { // All searches in this backend will always be considered indexed. return true; } @Override public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException { final long ret = getNumberOfChildren(entryDN); if(ret < 0) { return ConditionResult.UNDEFINED; } return ConditionResult.valueOf(ret != 0); } @Override public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException { checkNotNull(baseDN, "baseDN must not be null"); if (!baseDN.isRootDN()) { return -1; } long count = 1; for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) { DN subBase = entry.getKey(); Backend<?> b = entry.getValue(); Entry subBaseEntry = b.getEntry(subBase); if (subBaseEntry != null) { count++; count += b.getNumberOfEntriesInBaseDN(subBase); } } return count; } @Override public long getNumberOfChildren(DN parentDN) throws DirectoryException { checkNotNull(parentDN, "parentDN must not be null"); if (!parentDN.isRootDN()) { return -1; } long count = 0; for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) { DN subBase = entry.getKey(); Entry subBaseEntry = entry.getValue().getEntry(subBase); if (subBaseEntry != null) { count ++; } } return count; } @Override public Entry getEntry(DN entryDN) throws DirectoryException { // If the requested entry was the root DSE, then create and return it. if (entryDN == null || entryDN.isRootDN()) { return getRootDSE(); } // This method should never be used to get anything other than the root DSE. // If we got here, then that appears to be the case, so log a message. logger.warn(WARN_ROOTDSE_GET_ENTRY_NONROOT, entryDN); // Go ahead and check the subordinate backends to see if we can find the // entry there. Note that in order to avoid potential loop conditions, this // will only work if the set of subordinate bases has been explicitly // specified. if (subordinateBaseDNs != null) { for (Backend<?> b : subordinateBaseDNs.values()) { if (b.handlesEntry(entryDN)) { return b.getEntry(entryDN); } } } // If we've gotten here, then we couldn't find the entry so return null. return null; } /** * Retrieves the root DSE entry for the Directory Server. * * @return The root DSE entry for the Directory Server. */ public Entry getRootDSE() { return getRootDSE(null); } /** * Retrieves the root DSE entry for the Directory Server. * * @param connection * The client connection, or {@code null} if there is no associated * client connection. * @return The root DSE entry for the Directory Server. */ private Entry getRootDSE(ClientConnection connection) { Map<AttributeType, List<Attribute>> dseUserAttrs = new HashMap<>(); Map<AttributeType, List<Attribute>> dseOperationalAttrs = new HashMap<>(); Attribute publicNamingContextAttr = createAttribute( ATTR_NAMING_CONTEXTS, ATTR_NAMING_CONTEXTS_LC, DirectoryServer.getPublicNamingContexts().keySet()); addAttribute(publicNamingContextAttr, dseUserAttrs, dseOperationalAttrs); // Add the "ds-private-naming-contexts" attribute. Attribute privateNamingContextAttr = createAttribute( ATTR_PRIVATE_NAMING_CONTEXTS, ATTR_PRIVATE_NAMING_CONTEXTS, DirectoryServer.getPrivateNamingContexts().keySet()); addAttribute(privateNamingContextAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedControl" attribute. Attribute supportedControlAttr = createAttribute(ATTR_SUPPORTED_CONTROL, ATTR_SUPPORTED_CONTROL_LC, DirectoryServer.getSupportedControls()); addAttribute(supportedControlAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedExtension" attribute. Attribute supportedExtensionAttr = createAttribute( ATTR_SUPPORTED_EXTENSION, ATTR_SUPPORTED_EXTENSION_LC, DirectoryServer .getSupportedExtensions().keySet()); addAttribute(supportedExtensionAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedFeature" attribute. Attribute supportedFeatureAttr = createAttribute(ATTR_SUPPORTED_FEATURE, ATTR_SUPPORTED_FEATURE_LC, DirectoryServer.getSupportedFeatures()); addAttribute(supportedFeatureAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedSASLMechanisms" attribute. Attribute supportedSASLMechAttr = createAttribute( ATTR_SUPPORTED_SASL_MECHANISMS, ATTR_SUPPORTED_SASL_MECHANISMS_LC, DirectoryServer.getSupportedSASLMechanisms().keySet()); addAttribute(supportedSASLMechAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedLDAPVersions" attribute. TreeSet<String> versionStrings = new TreeSet<>(); for (Integer ldapVersion : DirectoryServer.getSupportedLDAPVersions()) { versionStrings.add(ldapVersion.toString()); } Attribute supportedLDAPVersionAttr = createAttribute( ATTR_SUPPORTED_LDAP_VERSION, ATTR_SUPPORTED_LDAP_VERSION_LC, versionStrings); addAttribute(supportedLDAPVersionAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedAuthPasswordSchemes" attribute. Attribute supportedAuthPWSchemesAttr = createAttribute( ATTR_SUPPORTED_AUTH_PW_SCHEMES, ATTR_SUPPORTED_AUTH_PW_SCHEMES_LC, DirectoryServer.getAuthPasswordStorageSchemes().keySet()); addAttribute(supportedAuthPWSchemesAttr, dseUserAttrs, dseOperationalAttrs); // Obtain TLS protocol and cipher support. Collection<String> supportedTlsProtocols; Collection<String> supportedTlsCiphers; if (connection != null) { // Only return the list of enabled protocols / ciphers for the connection // handler to which the client is connected. supportedTlsProtocols = connection.getConnectionHandler().getEnabledSSLProtocols(); supportedTlsCiphers = connection.getConnectionHandler().getEnabledSSLCipherSuites(); } else { try { final SSLContext context = SSLContext.getDefault(); final SSLParameters parameters = context.getSupportedSSLParameters(); supportedTlsProtocols = Arrays.asList(parameters.getProtocols()); supportedTlsCiphers = Arrays.asList(parameters.getCipherSuites()); } catch (Exception e) { // A default SSL context should always be available. supportedTlsProtocols = Collections.emptyList(); supportedTlsCiphers = Collections.emptyList(); } } // Add the "supportedTLSProtocols" attribute. Attribute supportedTLSProtocolsAttr = createAttribute( ATTR_SUPPORTED_TLS_PROTOCOLS, ATTR_SUPPORTED_TLS_PROTOCOLS_LC, supportedTlsProtocols); addAttribute(supportedTLSProtocolsAttr, dseUserAttrs, dseOperationalAttrs); // Add the "supportedTLSCiphers" attribute. Attribute supportedTLSCiphersAttr = createAttribute( ATTR_SUPPORTED_TLS_CIPHERS, ATTR_SUPPORTED_TLS_CIPHERS_LC, supportedTlsCiphers); addAttribute(supportedTLSCiphersAttr, dseUserAttrs, dseOperationalAttrs); addAll(staticDSEAttributes, dseUserAttrs, dseOperationalAttrs); addAll(userDefinedAttributes, dseUserAttrs, dseOperationalAttrs); // Construct and return the entry. Entry e = new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs, dseOperationalAttrs); e.processVirtualAttributes(); return e; } private void addAll(Collection<Attribute> attributes, Map<AttributeType, List<Attribute>> userAttrs, Map<AttributeType, List<Attribute>> operationalAttrs) { for (Attribute a : attributes) { AttributeType type = a.getAttributeType(); final Map<AttributeType, List<Attribute>> attrsMap = type.isOperational() && !showAllAttributes ? operationalAttrs : userAttrs; List<Attribute> attrs = attrsMap.get(type); if (attrs == null) { attrs = new ArrayList<>(); attrsMap.put(type, attrs); } attrs.add(a); } } private void addAttribute(Attribute attribute, Map<AttributeType, List<Attribute>> userAttrs, Map<AttributeType, List<Attribute>> operationalAttrs) { if (!attribute.isEmpty()) { List<Attribute> attrs = newArrayList(attribute); final AttributeType attrType = attribute.getAttributeType(); if (showAllAttributes || !attrType.isOperational()) { userAttrs.put(attrType, attrs); } else { operationalAttrs.put(attrType, attrs); } } } /** * Creates an attribute for the root DSE with the following * criteria. * * @param name * The name for the attribute. * @param lowerName * The name for the attribute formatted in all lowercase * characters. * @param values * The set of values to use for the attribute. * @return The constructed attribute. */ private Attribute createAttribute(String name, String lowerName, Collection<? extends Object> values) { AttributeType type = DirectoryServer.getAttributeTypeOrDefault(lowerName, name); AttributeBuilder builder = new AttributeBuilder(type, name); builder.addAllStrings(values); return builder.toAttribute(); } @Override public boolean entryExists(DN entryDN) throws DirectoryException { // If the specified DN was the null DN, then it exists. if (entryDN.isRootDN()) { return true; } // If it was not the null DN, then iterate through the associated // subordinate backends to make the determination. for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) { DN baseDN = entry.getKey(); if (entryDN.isDescendantOf(baseDN)) { Backend<?> b = entry.getValue(); if (b.entryExists(entryDN)) { return true; } } } return false; } @Override public void addEntry(Entry entry, AddOperation addOperation) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID())); } @Override public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID())); } @Override public void replaceEntry(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_ROOTDSE_MODIFY_NOT_SUPPORTED.get(newEntry.getName(), configEntryDN)); } @Override public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID())); } @Override public void search(SearchOperation searchOperation) throws DirectoryException, CanceledOperationException { DN baseDN = searchOperation.getBaseDN(); if (! baseDN.isRootDN()) { LocalizableMessage message = ERR_ROOTDSE_INVALID_SEARCH_BASE. get(searchOperation.getConnectionID(), searchOperation.getOperationID(), baseDN); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } SearchFilter filter = searchOperation.getFilter(); switch (searchOperation.getScope().asEnum()) { case BASE_OBJECT: Entry dseEntry = getRootDSE(searchOperation.getClientConnection()); if (filter.matchesEntry(dseEntry)) { searchOperation.returnEntry(dseEntry, null); } break; case SINGLE_LEVEL: for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) { searchOperation.checkIfCanceled(false); DN subBase = entry.getKey(); Backend<?> b = entry.getValue(); Entry subBaseEntry = b.getEntry(subBase); if (subBaseEntry != null && filter.matchesEntry(subBaseEntry)) { searchOperation.returnEntry(subBaseEntry, null); } } break; case WHOLE_SUBTREE: case SUBORDINATES: try { for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) { searchOperation.checkIfCanceled(false); DN subBase = entry.getKey(); Backend<?> b = entry.getValue(); searchOperation.setBaseDN(subBase); try { b.search(searchOperation); } catch (DirectoryException de) { // If it's a "no such object" exception, then the base entry for // the backend doesn't exist. This isn't an error, so ignore it. // We'll propogate all other errors, though. if (de.getResultCode() != ResultCode.NO_SUCH_OBJECT) { throw de; } } } } catch (DirectoryException de) { logger.traceException(de); throw de; } catch (Exception e) { logger.traceException(e); LocalizableMessage message = ERR_ROOTDSE_UNEXPECTED_SEARCH_FAILURE. get(searchOperation.getConnectionID(), searchOperation.getOperationID(), stackTraceToSingleLineString(e)); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, e); } finally { searchOperation.setBaseDN(rootDSEDN); } break; default: LocalizableMessage message = ERR_ROOTDSE_INVALID_SEARCH_SCOPE. get(searchOperation.getConnectionID(), searchOperation.getOperationID(), searchOperation.getScope()); throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); } } /** * Returns the subordinate base DNs of the root DSE. * * @return the subordinate base DNs of the root DSE */ @SuppressWarnings({ "unchecked", "rawtypes" }) public Map<DN, Backend<?>> getSubordinateBaseDNs() { if (subordinateBaseDNs != null) { return subordinateBaseDNs; } return (Map) DirectoryServer.getPublicNamingContexts(); } @Override public Set<String> getSupportedControls() { return Collections.emptySet(); } @Override public Set<String> getSupportedFeatures() { return Collections.emptySet(); } @Override public boolean supports(BackendOperation backendOperation) { // We will only export the DSE entry itself. return backendOperation.equals(BackendOperation.LDIF_EXPORT); } @Override public void exportLDIF(LDIFExportConfig exportConfig) throws DirectoryException { // Create the LDIF writer. LDIFWriter ldifWriter; try { ldifWriter = new LDIFWriter(exportConfig); } catch (Exception e) { logger.traceException(e); LocalizableMessage message = ERR_ROOTDSE_UNABLE_TO_CREATE_LDIF_WRITER.get( stackTraceToSingleLineString(e)); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); } // Write the root DSE entry itself to it. Make sure to close the LDIF // writer when we're done. try { ldifWriter.writeEntry(getRootDSE()); } catch (Exception e) { logger.traceException(e); LocalizableMessage message = ERR_ROOTDSE_UNABLE_TO_EXPORT_DSE.get(stackTraceToSingleLineString(e)); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); } finally { close(ldifWriter); } } @Override public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) throws DirectoryException { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID())); } @Override public void createBackup(BackupConfig backupConfig) throws DirectoryException { LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } @Override public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException { LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } @Override public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException { LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); } @Override public boolean isConfigurationAcceptable(RootDSEBackendCfg config, List<LocalizableMessage> unacceptableReasons, ServerContext serverContext) { return isConfigurationChangeAcceptable(config, unacceptableReasons); } @Override public boolean isConfigurationChangeAcceptable(RootDSEBackendCfg cfg, List<LocalizableMessage> unacceptableReasons) { boolean configIsAcceptable = true; try { Set<DN> subDNs = cfg.getSubordinateBaseDN(); if (subDNs.isEmpty()) { // This is fine -- we'll just use the set of user-defined suffixes. } else { for (DN baseDN : subDNs) { Backend<?> backend = DirectoryServer.getBackend(baseDN); if (backend == null) { unacceptableReasons.add(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(baseDN)); configIsAcceptable = false; } } } } catch (Exception e) { logger.traceException(e); unacceptableReasons.add(WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get( stackTraceToSingleLineString(e))); configIsAcceptable = false; } return configIsAcceptable; } @Override public ConfigChangeResult applyConfigurationChange(RootDSEBackendCfg cfg) { final ConfigChangeResult ccr = new ConfigChangeResult(); // Check to see if we should apply a new set of base DNs. ConcurrentHashMap<DN, Backend<?>> subBases; try { Set<DN> subDNs = cfg.getSubordinateBaseDN(); if (subDNs.isEmpty()) { // This is fine -- we'll just use the set of user-defined suffixes. subBases = null; } else { subBases = new ConcurrentHashMap<>(); for (DN baseDN : subDNs) { Backend<?> backend = DirectoryServer.getBackend(baseDN); if (backend == null) { // This is not fine. We can't use a suffix that doesn't exist. ccr.addMessage(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(baseDN)); ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); } else { subBases.put(baseDN, backend); } } } } catch (Exception e) { logger.traceException(e); ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); ccr.addMessage(WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get( stackTraceToSingleLineString(e))); subBases = null; } boolean newShowAll = cfg.isShowAllAttributes(); // Check to see if there is a new set of user-defined attributes. ArrayList<Attribute> userAttrs = new ArrayList<>(); try { ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN); addAllUserDefinedAttrs(userAttrs, configEntry.getEntry()); } catch (ConfigException e) { logger.traceException(e); ccr.addMessage(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get( configEntryDN, stackTraceToSingleLineString(e))); ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); } if (ccr.getResultCode() == ResultCode.SUCCESS) { subordinateBaseDNs = subBases; if (subordinateBaseDNs == null) { ccr.addMessage(INFO_ROOTDSE_USING_SUFFIXES_AS_BASE_DNS.get()); } else { String basesStr = "{ " + Utils.joinAsString(", ", subordinateBaseDNs.keySet()) + " }"; ccr.addMessage(INFO_ROOTDSE_USING_NEW_SUBORDINATE_BASE_DNS.get(basesStr)); } if (showAllAttributes != newShowAll) { showAllAttributes = newShowAll; ccr.addMessage(INFO_ROOTDSE_UPDATED_SHOW_ALL_ATTRS.get( ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES, showAllAttributes)); } userDefinedAttributes = userAttrs; ccr.addMessage(INFO_ROOTDSE_USING_NEW_USER_ATTRS.get()); } return ccr; } }