RootDSEBackend.java revision 8ed24d57f423f11acc2a5c0ccdea5d3e685d8189
/*
* 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.opends.messages.BackendMessages.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.config.ConfigConstants.*;
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 ArrayList<Attribute> staticDSEAttributes;
/**
* The set of user-defined attributes that will be included in the root DSE
* entry.
*/
private ArrayList<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 subordinate base DNs and their associated backends that will be
* used for non-base searches.
*/
private ConcurrentHashMap<DN, Backend<?>> subordinateBaseDNs;
/** The set of objectclasses that will be used in the root DSE entry. */
private HashMap<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;
/**
* 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.
}
/** {@inheritDoc} */
@Override
public void configureBackend(RootDSEBackendCfg config, ServerContext serverContext) throws ConfigException
{
Reject.ifNull(config);
currentConfig = config;
configEntryDN = config.dn();
}
/** {@inheritDoc} */
@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<Attribute>();
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<DN, Backend<?>>();
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<Attribute>();
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<ObjectClass,String>(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(ArrayList<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);
}
}
}
}
/** {@inheritDoc} */
@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);
}
/** {@inheritDoc} */
@Override
public DN[] getBaseDNs()
{
return baseDNs;
}
/** {@inheritDoc} */
@Override
public synchronized long getEntryCount()
{
// There is always just a single entry in this backend.
return 1;
}
/** {@inheritDoc} */
@Override
public boolean isIndexed(AttributeType attributeType, IndexType indexType)
{
// All searches in this backend will always be considered indexed.
return true;
}
/** {@inheritDoc} */
@Override
public ConditionResult hasSubordinates(DN entryDN)
throws DirectoryException
{
long ret = numSubordinates(entryDN, false);
if(ret < 0)
{
return ConditionResult.UNDEFINED;
}
return ConditionResult.valueOf(ret != 0);
}
/** {@inheritDoc} */
@Override
public long numSubordinates(DN entryDN, boolean subtree)
throws DirectoryException
{
if (entryDN == null || ! entryDN.isRootDN())
{
return -1;
}
long count = 0;
for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
{
DN subBase = entry.getKey();
Backend<?> b = entry.getValue();
Entry subBaseEntry = b.getEntry(subBase);
if (subBaseEntry != null)
{
if(subtree)
{
long subCount = b.numSubordinates(subBase, true);
if(subCount < 0)
{
return -1;
}
count += subCount;
}
count ++;
}
}
return count;
}
/** {@inheritDoc} */
@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)
{
HashMap<AttributeType,List<Attribute>> dseUserAttrs =
new HashMap<AttributeType,List<Attribute>>();
HashMap<AttributeType,List<Attribute>> dseOperationalAttrs =
new HashMap<AttributeType,List<Attribute>>();
Attribute publicNamingContextAttr = createDNAttribute(
ATTR_NAMING_CONTEXTS, ATTR_NAMING_CONTEXTS_LC,
DirectoryServer.getPublicNamingContexts().keySet());
addAttribute(publicNamingContextAttr, dseUserAttrs, dseOperationalAttrs);
// Add the "ds-private-naming-contexts" attribute.
Attribute privateNamingContextAttr = createDNAttribute(
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<String>();
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.
Set<String> authPWSchemes =
DirectoryServer.getAuthPasswordStorageSchemes().keySet();
if (!authPWSchemes.isEmpty())
{
Attribute supportedAuthPWSchemesAttr =
createAttribute(ATTR_SUPPORTED_AUTH_PW_SCHEMES,
ATTR_SUPPORTED_AUTH_PW_SCHEMES_LC, authPWSchemes);
ArrayList<Attribute> supportedAuthPWSchemesAttrs =
new ArrayList<Attribute>(1);
supportedAuthPWSchemesAttrs.add(supportedAuthPWSchemesAttr);
if (showAllAttributes
|| !supportedSASLMechAttr.getAttributeType().isOperational())
{
dseUserAttrs.put(supportedAuthPWSchemesAttr.getAttributeType(),
supportedAuthPWSchemesAttrs);
}
else
{
dseOperationalAttrs.put(supportedAuthPWSchemesAttr.getAttributeType(),
supportedAuthPWSchemesAttrs);
}
}
// 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(ArrayList<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<Attribute>();
attrsMap.put(type, attrs);
}
attrs.add(a);
}
}
private void addAttribute(Attribute publicNamingContextAttr,
HashMap<AttributeType, List<Attribute>> userAttrs,
HashMap<AttributeType, List<Attribute>> operationalAttrs)
{
if (!publicNamingContextAttr.isEmpty())
{
List<Attribute> privateNamingContextAttrs = new ArrayList<Attribute>(1);
privateNamingContextAttrs.add(publicNamingContextAttr);
final AttributeType attrType = publicNamingContextAttr.getAttributeType();
if (showAllAttributes || !attrType.isOperational())
{
userAttrs.put(attrType, privateNamingContextAttrs);
}
else
{
operationalAttrs.put(attrType, privateNamingContextAttrs);
}
}
}
/**
* Creates an attribute for the root DSE meant to hold a set of DNs.
*
* @param name The name for the attribute.
* @param lowerName The name for the attribute formatted in all lowercase
* characters.
* @param values The set of DN values to use for the attribute.
*
* @return The constructed attribute.
*/
private Attribute createDNAttribute(String name, String lowerName,
Collection<DN> values)
{
AttributeType type = DirectoryServer.getAttributeType(lowerName);
if (type == null)
{
type = DirectoryServer.getDefaultAttributeType(name);
}
AttributeBuilder builder = new AttributeBuilder(type, name);
for (DN dn : values) {
builder.add(dn.toString());
}
return builder.toAttribute();
}
/**
* 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<String> values)
{
AttributeType type = DirectoryServer.getAttributeType(lowerName);
if (type == null)
{
type = DirectoryServer.getDefaultAttributeType(name);
}
AttributeBuilder builder = new AttributeBuilder(type, name);
builder.setInitialCapacity(values.size());
for (String s : values) {
builder.add(s);
}
return builder.toAttribute();
}
/** {@inheritDoc} */
@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;
}
/** {@inheritDoc} */
@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()));
}
/** {@inheritDoc} */
@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()));
}
/** {@inheritDoc} */
@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));
}
/** {@inheritDoc} */
@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()));
}
/** {@inheritDoc} */
@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();
}
/** {@inheritDoc} */
@Override
public Set<String> getSupportedControls()
{
return Collections.emptySet();
}
/** {@inheritDoc} */
@Override
public Set<String> getSupportedFeatures()
{
return Collections.emptySet();
}
/** {@inheritDoc} */
@Override
public boolean supports(BackendOperation backendOperation)
{
// We will only export the DSE entry itself.
return backendOperation.equals(BackendOperation.LDIF_EXPORT);
}
/** {@inheritDoc} */
@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);
}
}
/** {@inheritDoc} */
@Override
public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
throws DirectoryException
{
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID()));
}
/** {@inheritDoc} */
@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);
}
/** {@inheritDoc} */
@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);
}
/** {@inheritDoc} */
@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);
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationAcceptable(RootDSEBackendCfg config,
List<LocalizableMessage> unacceptableReasons,
ServerContext serverContext)
{
return isConfigurationChangeAcceptable(config, unacceptableReasons);
}
/** {@inheritDoc} */
@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;
}
/** {@inheritDoc} */
@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<DN, Backend<?>>();
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<Attribute>();
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;
}
/** {@inheritDoc} */
@Override
public void preloadEntryCache() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Operation not supported.");
}
}