opends/resource/schema/02-config.ldif
@@ -995,6 +995,9 @@ attributeTypes: ( 1.3.6.1.4.1.26027.1.1.289 NAME 'ds-pwp-password-policy-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.291 NAME 'ds-private-naming-contexts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled ) opends/src/server/org/opends/server/api/Backend.java
@@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; @@ -400,10 +401,18 @@ * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the * requested control, or <CODE>false</CODE> * @return {@code true} if this backends supports the control with * the specified OID, or {@code false} if it does not. */ public abstract boolean supportsControl(String controlOID); public final boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); Set<String> supportedControls = getSupportedControls(); return ((supportedControls != null) && supportedControls.contains(controlOID)); } @@ -424,10 +433,18 @@ * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the * requested feature, or <CODE>false</CODE> * @return {@code true} if this backend supports the feature with * the specified OID, or {@code false} if it does not. */ public abstract boolean supportsFeature(String featureOID); public final boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); Set<String> supportedFeatures = getSupportedFeatures(); return ((supportedFeatures != null) && supportedFeatures.contains(featureOID)); } @@ -892,17 +909,66 @@ synchronized (this) { LinkedHashSet<Backend> backendSet = new LinkedHashSet<Backend>(); for (Backend b : subordinateBackends) { backendSet.add(b); } if (backendSet.add(subordinateBackend)) { Backend[] newSubordinateBackends = new Backend[subordinateBackends.length+1]; System.arraycopy(subordinateBackends, 0, newSubordinateBackends, 0, subordinateBackends.length); newSubordinateBackends[subordinateBackends.length] = subordinateBackend; new Backend[backendSet.size()]; backendSet.toArray(newSubordinateBackends); subordinateBackends = newSubordinateBackends; } } } /** * Removes the provided backend from the set of subordinate backends * for this backend. * * @param subordinateBackend The backend to remove from the set of * subordinate backends for this * backend. */ public void removeSubordinateBackend(Backend subordinateBackend) { assert debugEnter(CLASS_NAME, "removeSubordinateBackend", String.valueOf(subordinateBackend)); synchronized (this) { ArrayList<Backend> backendList = new ArrayList<Backend>(subordinateBackends.length); boolean found = false; for (Backend b : subordinateBackends) { if (b.equals(subordinateBackend)) { found = true; } else { backendList.add(b); } } if (found) { Backend[] newSubordinateBackends = new Backend[backendList.size()]; backendList.toArray(newSubordinateBackends); subordinateBackends = newSubordinateBackends; } } } opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -275,7 +275,19 @@ // Register the backup base as a private suffix. DirectoryServer.registerPrivateSuffix(backupBaseDN, this); try { DirectoryServer.registerBaseDN(backupBaseDN, this, true, false); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackend", e); msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; String message = getMessage(msgID, backupBaseDN.toString(), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } } @@ -296,6 +308,15 @@ assert debugEnter(CLASS_NAME, "finalizeBackend"); DirectoryServer.deregisterConfigurableComponent(this); try { DirectoryServer.deregisterBaseDN(backupBaseDN, false); } catch (Exception e) { assert debugException(CLASS_NAME, "finalizeBackend", e); } } @@ -1029,26 +1050,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); // This backend does not provide any special control support. return false; } /** * Retrieves the OIDs of the features that may be supported by this backend. * * @return The OIDs of the features that may be supported by this backend. @@ -1063,26 +1064,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); // This backend does not provide any special feature support. return false; } /** * Indicates whether this backend provides a mechanism to export the data it * contains to an LDIF file. * opends/src/server/org/opends/server/backends/MemoryBackend.java
@@ -48,6 +48,7 @@ import org.opends.server.types.DirectoryException; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.InitializationException; import org.opends.server.types.LDIFExportConfig; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.RestoreConfig; @@ -62,6 +63,7 @@ import static org.opends.server.messages.BackendMessages.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; @@ -148,7 +150,7 @@ */ public synchronized void initializeBackend(ConfigEntry configEntry, DN[] baseDNs) throws ConfigException throws ConfigException, InitializationException { assert debugEnter(CLASS_NAME, "initializeBackend", String.valueOf(configEntry), String.valueOf(baseDNs)); @@ -184,7 +186,19 @@ for (DN dn : baseDNs) { DirectoryServer.registerSuffix(dn, this); try { DirectoryServer.registerBaseDN(dn, this, false, false); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackend", e); int msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; String message = getMessage(msgID, dn.toString(), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } } } @@ -209,6 +223,18 @@ assert debugEnter(CLASS_NAME, "finalizeBackend"); clearMemoryBackend(); for (DN dn : baseDNs) { try { DirectoryServer.deregisterBaseDN(dn, false); } catch (Exception e) { assert debugException(CLASS_NAME, "finalizeBackend", e); } } } @@ -585,19 +611,6 @@ /** * {@inheritDoc} */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); return supportedControls.contains(controlOID); } /** * {@inheritDoc} */ public HashSet<String> getSupportedFeatures() { assert debugEnter(CLASS_NAME, "getSupportedFeatures"); @@ -610,20 +623,6 @@ /** * {@inheritDoc} */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); // This backend does not provide any special feature support. return false; } /** * {@inheritDoc} */ public boolean supportsLDIFExport() { assert debugEnter(CLASS_NAME, "supportsLDIFExport"); opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -250,7 +250,19 @@ // Register the monitor base as a private suffix. DirectoryServer.registerPrivateSuffix(baseMonitorDN, this); try { DirectoryServer.registerBaseDN(baseMonitorDN, this, true, false); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackend", e); int msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; String message = getMessage(msgID, baseMonitorDN.toString(), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } } @@ -271,6 +283,15 @@ assert debugEnter(CLASS_NAME, "finalizeBackend"); DirectoryServer.deregisterConfigurableComponent(this); try { DirectoryServer.deregisterBaseDN(baseMonitorDN, false); } catch (Exception e) { assert debugException(CLASS_NAME, "finalizeBackend", e); } } @@ -929,26 +950,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); // This backend does not provide any special control support. return false; } /** * Retrieves the OIDs of the features that may be supported by this backend. * * @return The OIDs of the features that may be supported by this backend. @@ -963,26 +964,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); // This backend does not provide any special feature support. return false; } /** * Indicates whether this backend provides a mechanism to export the data it * contains to an LDIF file. * opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -502,21 +502,42 @@ // Add the "namingContexts" attribute. Attribute namingContextAttr = Attribute publicNamingContextAttr = createDNAttribute(ATTR_NAMING_CONTEXTS, ATTR_NAMING_CONTEXTS_LC, DirectoryServer.getSuffixes().keySet()); ArrayList<Attribute> namingContextAttrs = new ArrayList<Attribute>(1); namingContextAttrs.add(namingContextAttr); DirectoryServer.getPublicNamingContexts().keySet()); ArrayList<Attribute> publicNamingContextAttrs = new ArrayList<Attribute>(1); publicNamingContextAttrs.add(publicNamingContextAttr); if (showAllAttributes || (! namingContextAttr.getAttributeType().isOperational())) (! publicNamingContextAttr.getAttributeType().isOperational())) { dseUserAttrs.put(namingContextAttr.getAttributeType(), namingContextAttrs); dseUserAttrs.put(publicNamingContextAttr.getAttributeType(), publicNamingContextAttrs); } else { dseOperationalAttrs.put(namingContextAttr.getAttributeType(), namingContextAttrs); dseOperationalAttrs.put(publicNamingContextAttr.getAttributeType(), publicNamingContextAttrs); } // Add the "ds-private-naming-contexts" attribute. Attribute privateNamingContextAttr = createDNAttribute(ATTR_PRIVATE_NAMING_CONTEXTS, ATTR_PRIVATE_NAMING_CONTEXTS, DirectoryServer.getPrivateNamingContexts().keySet()); ArrayList<Attribute> privateNamingContextAttrs = new ArrayList<Attribute>(1); privateNamingContextAttrs.add(privateNamingContextAttr); if (showAllAttributes || (! privateNamingContextAttr.getAttributeType().isOperational())) { dseUserAttrs.put(privateNamingContextAttr.getAttributeType(), privateNamingContextAttrs); } else { dseOperationalAttrs.put(privateNamingContextAttr.getAttributeType(), privateNamingContextAttrs); } @@ -858,7 +879,7 @@ Map<DN,Backend> baseMap; if (subordinateBaseDNs == null) { baseMap = DirectoryServer.getSuffixes(); baseMap = DirectoryServer.getPublicNamingContexts(); } else { @@ -1043,7 +1064,7 @@ Map<DN,Backend> baseMap; if (subordinateBaseDNs == null) { baseMap = DirectoryServer.getSuffixes(); baseMap = DirectoryServer.getPublicNamingContexts(); } else { @@ -1072,7 +1093,7 @@ case SUBORDINATE_SUBTREE: if (subordinateBaseDNs == null) { baseMap = DirectoryServer.getSuffixes(); baseMap = DirectoryServer.getPublicNamingContexts(); } else { @@ -1158,26 +1179,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); // This backend does not provide any special control support. return false; } /** * Retrieves the OIDs of the features that may be supported by this backend. * * @return The OIDs of the features that may be supported by this backend. @@ -1192,26 +1193,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); // This backend does not provide any special feature support. return false; } /** * Indicates whether this backend provides a mechanism to export the data it * contains to an LDIF file. * opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -251,11 +251,22 @@ // Register each of the suffixes with the Directory Server. Also, register // the first one as the schema base. this.baseDNs = baseDNs; DirectoryServer.registerPrivateSuffix(baseDNs[0], this); DirectoryServer.setSchemaDN(baseDNs[0]); for (int i=1; i < baseDNs.length; i++) for (int i=0; i < baseDNs.length; i++) { DirectoryServer.registerPrivateSuffix(baseDNs[i], this); try { DirectoryServer.registerBaseDN(baseDNs[i], this, true, false); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackend", e); msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; String message = getMessage(msgID, baseDNs[i].toString(), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } } @@ -306,6 +317,18 @@ assert debugEnter(CLASS_NAME, "finalizeBackend"); DirectoryServer.deregisterConfigurableComponent(this); for (DN baseDN : baseDNs) { try { DirectoryServer.deregisterBaseDN(baseDN, false); } catch (Exception e) { assert debugException(CLASS_NAME, "finalizeBackend", e); } } } @@ -801,26 +824,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); // This backend does not provide any special control support. return false; } /** * Retrieves the OIDs of the features that may be supported by this backend. * * @return The OIDs of the features that may be supported by this backend. @@ -835,26 +838,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); // This backend does not provide any special feature support. return false; } /** * Indicates whether this backend provides a mechanism to export the data it * contains to an LDIF file. * @@ -2227,7 +2210,7 @@ { try { DirectoryServer.deregisterSuffix(dn); DirectoryServer.deregisterBaseDN(dn, false); if (detailedResults) { msgID = MSGID_SCHEMA_DEREGISTERED_BASE_DN; @@ -2250,7 +2233,7 @@ { try { DirectoryServer.registerPrivateSuffix(dn, this); DirectoryServer.registerBaseDN(dn, this, true, false); if (detailedResults) { msgID = MSGID_SCHEMA_REGISTERED_BASE_DN; opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -63,6 +63,7 @@ import org.opends.server.types.ResultCode; import org.opends.server.util.LDIFException; import static org.opends.server.messages.BackendMessages.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.messages.JebMessages.*; import static org.opends.server.loggers.Error.logError; @@ -293,10 +294,21 @@ config = new Config(); config.initializeConfig(configEntry, baseDNs); // FIXME: Currently assuming every base DN is also a suffix. for (DN dn : baseDNs) { DirectoryServer.registerSuffix(dn, this); try { DirectoryServer.registerBaseDN(dn, this, false, false); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackend", e); int msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; String message = getMessage(msgID, String.valueOf(dn), String.valueOf(e)); throw new InitializationException(msgID, message, e); } } /* @@ -402,9 +414,9 @@ { try { DirectoryServer.deregisterSuffix(dn); DirectoryServer.deregisterBaseDN(dn, false); } catch (ConfigException e) catch (Exception e) { assert debugException(CLASS_NAME, "finalizeBackend", e); } @@ -571,23 +583,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature"); return false; //NYI } /** * Retrieves the OIDs of the controls that may be supported by this backend. * * @return The OIDs of the controls that may be supported by this backend. @@ -602,23 +597,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl"); return supportedControls.contains(controlOID); } /** * Retrieves the set of base-level DNs that may be used within this backend. * * @return The set of base-level DNs that may be used within this backend. @@ -1365,6 +1343,8 @@ assert debugEnter(CLASS_NAME, "applyNewConfiguration"); ConfigChangeResult ccr; ResultCode resultCode = ResultCode.SUCCESS; ArrayList<String> messages = new ArrayList<String>(); try { @@ -1397,7 +1377,7 @@ // Even though access to the entry container map is safe, there may be // operation threads with a handle on the entry container being // closed. DirectoryServer.deregisterSuffix(baseDN); DirectoryServer.deregisterBaseDN(baseDN, false); rootContainer.removeEntryContainer(baseDN); } } @@ -1406,9 +1386,24 @@ { if (!rootContainer.getBaseDNs().contains(baseDN)) { try { // The base DN was added. rootContainer.openEntryContainer(baseDN); DirectoryServer.registerSuffix(baseDN, this); DirectoryServer.registerBaseDN(baseDN, this, false, false); } catch (Exception e) { assert debugException(CLASS_NAME, "applyNewConfiguration", e); resultCode = DirectoryServer.getServerErrorResultCode(); int msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; messages.add(getMessage(msgID, String.valueOf(baseDN), String.valueOf(e))); ccr = new ConfigChangeResult(resultCode, false, messages); return ccr; } } } @@ -1420,14 +1415,13 @@ } catch (Exception e) { ArrayList<String> messages = new ArrayList<String>(); messages.add(e.getMessage()); ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(), false, messages); return ccr; } ccr = new ConfigChangeResult(ResultCode.SUCCESS, false); ccr = new ConfigChangeResult(resultCode, false, messages); return ccr; } opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -336,7 +336,19 @@ // Register the task base as a private suffix. DirectoryServer.registerPrivateSuffix(baseDNs[0], this); try { DirectoryServer.registerBaseDN(taskRootDN, this, true, false); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackend", e); msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN; String message = getMessage(msgID, taskRootDN.toString(), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } } @@ -381,6 +393,15 @@ { assert debugException(CLASS_NAME, "finalizeBackend", e); } try { DirectoryServer.deregisterBaseDN(taskRootDN, false); } catch (Exception e) { assert debugException(CLASS_NAME, "finalizeBackend", e); } } @@ -971,26 +992,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { assert debugEnter(CLASS_NAME, "supportsControl", String.valueOf(controlOID)); // This backend does not provide any special control support. return false; } /** * Retrieves the OIDs of the features that may be supported by this backend. * * @return The OIDs of the features that may be supported by this backend. @@ -1005,26 +1006,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { assert debugEnter(CLASS_NAME, "supportsFeature", String.valueOf(featureOID)); // This backend does not provide any special feature support. return false; } /** * Indicates whether this backend provides a mechanism to export the data it * contains to an LDIF file. * opends/src/server/org/opends/server/core/AddOperation.java
@@ -1018,7 +1018,7 @@ if (parentDN == null) { // Either this entry is a suffix or doesn't belong in the directory. if (DirectoryServer.isSuffix(entryDN)) if (DirectoryServer.isNamingContext(entryDN)) { // This is fine. This entry is one of the configured suffixes. parentLock = null; opends/src/server/org/opends/server/core/BackendConfigManager.java
@@ -48,6 +48,7 @@ import org.opends.server.config.MultiChoiceConfigAttribute; import org.opends.server.config.StringConfigAttribute; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DirectoryException; import org.opends.server.types.DN; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; @@ -542,7 +543,21 @@ // Register the backend with the server. try { DirectoryServer.registerBackend(backend); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeBackendConfig", e); msgID = MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND; String message = getMessage(msgID, backendID, stackTraceToSingleLineString(e)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR, message, msgID); // FIXME -- Do we need to send an admin alert? } // Put this backend in the hash so that we will be able to find it if it @@ -711,6 +726,64 @@ unacceptableReason.append(getMessage(msgID, String.valueOf(backendDN))); return false; } // See if the backend is registered with the server. If it is, then // see what's changed and whether those changes are acceptable. Backend backend = registeredBackends.get(configEntryDN); if (backend != null) { LinkedHashSet<DN> removedDNs = new LinkedHashSet<DN>(); for (DN dn : backend.getBaseDNs()) { removedDNs.add(dn); } LinkedHashSet<DN> addedDNs = new LinkedHashSet<DN>(); for (DN dn : baseDNAttr.pendingValues()) { addedDNs.add(dn); } Iterator<DN> iterator = removedDNs.iterator(); while (iterator.hasNext()) { DN dn = iterator.next(); if (addedDNs.remove(dn)) { iterator.remove(); } } for (DN dn : addedDNs) { try { DirectoryServer.registerBaseDN(dn, backend, false, true); } catch (DirectoryException de) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", de); unacceptableReason.append(de.getMessage()); return false; } } for (DN dn : removedDNs) { try { DirectoryServer.deregisterBaseDN(dn, true); } catch (DirectoryException de) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", de); unacceptableReason.append(de.getMessage()); return false; } } } } catch (Exception e) { @@ -1266,7 +1339,28 @@ } // Register the backend with the server. try { DirectoryServer.registerBackend(backend); } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); msgID = MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND; String message = getMessage(msgID, backendID, stackTraceToSingleLineString(e)); resultCode = DirectoryServer.getServerErrorResultCode(); messages.add(message); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR, message, msgID); // FIXME -- Do we need to send an admin alert? return new ConfigChangeResult(resultCode, adminActionRequired, messages); } registeredBackends.put(backendDN, backend); @@ -1443,6 +1537,7 @@ // See if the entry contains an attribute that specifies the set of base DNs // for the backend. If it does not, then skip it. List<DN> baseDNs = null; msgID = MSGID_CONFIG_BACKEND_ATTR_DESCRIPTION_BASE_DNS; DNConfigAttribute baseDNStub = new DNConfigAttribute(ATTR_BACKEND_BASE_DN, getMessage(msgID), true, @@ -1458,6 +1553,10 @@ unacceptableReason.append(getMessage(msgID, String.valueOf(backendDN))); return false; } else { baseDNs = baseDNAttr.pendingValues(); } } catch (Exception e) { @@ -1554,6 +1653,25 @@ } // Make sure that all of the base DNs are acceptable for use in the server. for (DN baseDN : baseDNs) { try { DirectoryServer.registerBaseDN(baseDN, backend, false, true); } catch (DirectoryException de) { unacceptableReason.append(de.getMessage()); return false; } catch (Exception e) { unacceptableReason.append(stackTraceToSingleLineString(e)); return false; } } // If we've gotten to this point, then it is acceptable as far as we are // concerned. If it is unacceptable according to the configuration for that @@ -1960,7 +2078,29 @@ // At this point, the backend should be online. Add it as one of the // registered backends for this backend config manager. try { DirectoryServer.registerBackend(backend); } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationAdd", e); msgID = MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND; String message = getMessage(msgID, backendID, stackTraceToSingleLineString(e)); resultCode = DirectoryServer.getServerErrorResultCode(); messages.add(message); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR, message, msgID); // FIXME -- Do we need to send an admin alert? return new ConfigChangeResult(resultCode, adminActionRequired, messages); } registeredBackends.put(backendDN, backend); return new ConfigChangeResult(resultCode, adminActionRequired, messages); } @@ -1995,7 +2135,7 @@ // do know about it, then that means that it is enabled and we will not // allow removing a backend that is enabled. Backend backend = registeredBackends.get(backendDN); if (backendDN == null) if (backend == null) { return true; } @@ -2041,7 +2181,7 @@ // See if this backend config manager has a backend registered with the // provided DN. If not, then we don't care if the entry is deleted. Backend backend = registeredBackends.get(backendDN); if (backendDN == null) if (backend == null) { return new ConfigChangeResult(resultCode, adminActionRequired, messages); @@ -2053,6 +2193,19 @@ Backend[] subBackends = backend.getSubordinateBackends(); if ((subBackends == null) || (subBackends.length == 0)) { registeredBackends.remove(backendDN); try { backend.finalizeBackend(); } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationDelete", e); } DirectoryServer.deregisterBackend(backend); return new ConfigChangeResult(resultCode, adminActionRequired, messages); } opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -34,8 +34,11 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -161,6 +164,7 @@ import static org.opends.server.util.DynamicConstants.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.util.Validator.*; @@ -398,19 +402,6 @@ // The key manager provider configuration manager for the Directory Server. private KeyManagerProviderConfigManager keyManagerProviderConfigManager; // The set of "private" suffixes that will be used to provide server-generated // data (e.g., monitor information, schema, etc.) to clients but will not be // searchable by default. private LinkedHashMap<DN,Backend> privateSuffixes; // The set of backends that have been registered with the server (mapped // between the normalized suffix and the backend). private LinkedHashMap<DN,Backend> suffixes; // The set of backends that have been registered with the server (mapped // between their backend ID and the backend). private LinkedHashMap<String,Backend> backends; // The set of connections that are currently established. private LinkedHashSet<ClientConnection> establishedConnections; @@ -498,6 +489,18 @@ // The thread group for all threads associated with the Directory Server. private ThreadGroup directoryThreadGroup; // The set of base DNs registered with the server. private TreeMap<DN,Backend> baseDNs; // The set of private naming contexts registered with the server. private TreeMap<DN,Backend> privateNamingContexts; // The set of public naming contexts registered with the server. private TreeMap<DN,Backend> publicNamingContexts; // The set of backends registered with the server. private TreeMap<String,Backend> backends; // The set of supported controls registered with the Directory Server. private TreeSet<String> supportedControls; @@ -606,9 +609,10 @@ directoryServer.defaultPasswordPolicy = null; directoryServer.monitorProviders = new ConcurrentHashMap<String,MonitorProvider>(); directoryServer.privateSuffixes = new LinkedHashMap<DN,Backend>(); directoryServer.suffixes = new LinkedHashMap<DN,Backend>(); directoryServer.backends = new LinkedHashMap<String,Backend>(); directoryServer.backends = new TreeMap<String,Backend>(); directoryServer.baseDNs = new TreeMap<DN,Backend>(); directoryServer.publicNamingContexts = new TreeMap<DN,Backend>(); directoryServer.privateNamingContexts = new TreeMap<DN,Backend>(); directoryServer.changeNotificationListeners = new CopyOnWriteArrayList<ChangeNotificationListener>(); directoryServer.persistentSearches = @@ -5210,12 +5214,12 @@ /** * Retrieves the set of backends that have been registered with the Directory * Server. * Server, as a mapping between the backend ID and the corresponding backend. * * @return The set of backends that have been registered with the Directory * Server. */ public static LinkedHashMap<String,Backend> getBackends() public static Map<String,Backend> getBackends() { assert debugEnter(CLASS_NAME, "getBackends"); @@ -5229,7 +5233,7 @@ * * @param backendID The backend ID of the backend to retrieve. * * @return The backend with the specified backend ID, or <CODE>null</CODE> if * @return The backend with the specified backend ID, or {@code null} if * there is none. */ public static Backend getBackend(String backendID) @@ -5247,8 +5251,8 @@ * * @param backendID The backend ID for which to make the determination. * * @return <CODE>true</CODE> if the Directory Server has a backend with the * specified backend ID, or <CODE>false</CODE> if not. * @return {@code true} if the Directory Server has a backend with the * specified backend ID, or {@code false} if not. */ public static boolean hasBackend(String backendID) { @@ -5264,13 +5268,40 @@ * will not register the set of configured suffixes with the server, as that * must be done by the backend itself. * * @param backend The backend to register with the server. * @param backend The backend to register with the server. Neither the * backend nor its backend ID may be null. * * @throws DirectoryException If the backend ID for the provided backend * conflicts with the backend ID of an existing * backend. */ public static void registerBackend(Backend backend) throws DirectoryException { assert debugEnter(CLASS_NAME, "registerBackend", String.valueOf(backend)); directoryServer.backends.put(backend.getBackendID(), backend); ensureNotNull(backend); String backendID = backend.getBackendID(); ensureNotNull(backendID); synchronized (directoryServer) { TreeMap<String,Backend> newBackends = new TreeMap<String,Backend>(directoryServer.backends); if (newBackends.containsKey(backendID)) { int msgID = MSGID_REGISTER_BACKEND_ALREADY_EXISTS; String message = getMessage(msgID, backendID); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, msgID); } else { newBackends.put(backendID, backend); directoryServer.backends = newBackends; } } } @@ -5280,366 +5311,521 @@ * will not deregister the set of configured suffixes with the server, as that * must be done by the backend itself. * * @param backend the backend to deregister with the server. * @param backend The backend to deregister with the server. It must not be * {@code null}. */ public static void deregisterBackend(Backend backend) { assert debugEnter(CLASS_NAME, "deregisterBackend", String.valueOf(backend)); directoryServer.backends.remove(backend.getBackendID()); ensureNotNull(backend); synchronized (directoryServer) { TreeMap<String,Backend> newBackends = new TreeMap<String,Backend>(directoryServer.backends); newBackends.remove(backend.getBackendID()); directoryServer.backends = newBackends; } } /** * Retrieves the set of suffixes that have been registered with the Directory * Server. * Retrieves the entire set of base DNs registered with the Directory Server, * mapped from the base DN to the backend responsible for that base DN. The * same backend may be present multiple times, mapped from different base DNs. * * @return The set of suffixes that have been registered with the Directory * Server. * @return The entire set of base DNs registered with the Directory Server. */ public static LinkedHashMap<DN,Backend> getSuffixes() public static Map<DN,Backend> getBaseDNs() { assert debugEnter(CLASS_NAME, "getSuffixes"); assert debugEnter(CLASS_NAME, "getBaseDNs"); return directoryServer.suffixes; return directoryServer.baseDNs; } /** * Retrieves the set of "private" suffixes that have been registered with the * server that will provide server-specific data to clients (e.g., monitor * data, schema, etc.) but should not be considered for normal operations * that may target all "user" suffixes. * Retrieves the backend with the specified base DN. * * @return The set of "private" suffixes that have been registered with the * server. * @param baseDN The DN that is registered as one of the base DNs for the * backend to retrieve. * * @return The backend with the specified base DN, or {@code null} if there * is no backend registered with the specified base DN. */ public static LinkedHashMap<DN,Backend> getPrivateSuffixes() public static Backend getBackendWithBaseDN(DN baseDN) { assert debugEnter(CLASS_NAME, "getPrivateSuffixes"); assert debugEnter(CLASS_NAME, "getBackendWithBaseDN", String.valueOf(baseDN)); return directoryServer.privateSuffixes; return directoryServer.baseDNs.get(baseDN); } /** * Indicates whether the provided DN is one of the suffixes defined in the * Directory Server. * Retrieves the backend that should be used to handle operations on the * specified entry. * * @param dn The DN for which to make the determination. * @param entryDN The DN of the entry for which to retrieve the * corresponding backend. * * @return <CODE>true</CODE> if the provided DN is one of the suffixes * defined in the Directory Server, or <CODE>false</CODE> if not. * @return The backend that should be used to handle operations on the * specified entry, or {@code null} if no appropriate backend is * registered with the server. */ public static boolean isSuffix(DN dn) public static Backend getBackend(DN entryDN) { assert debugEnter(CLASS_NAME, "isSuffix", String.valueOf(dn)); assert debugEnter(CLASS_NAME, "getBackendForEntry", String.valueOf(entryDN)); return (directoryServer.suffixes.containsKey(dn) || directoryServer.privateSuffixes.containsKey(dn)); } /** * Retrieves the backend that should be used to handle operations for the * provided entry DN. * * @param dn The DN of the entry for which to retrieve the appropriate * backend. * * @return The backend that should be used to handle the provided DN, or * <CODE>null</CODE> if there is no backend for the provided DN. */ public static Backend getBackend(DN dn) { assert debugEnter(CLASS_NAME, "getBackend", String.valueOf(dn)); if (dn.isNullDN()) if (entryDN.isNullDN()) { return directoryServer.rootDSEBackend; } Backend backend = directoryServer.suffixes.get(dn); if (backend == null) TreeMap<DN,Backend> baseDNs = directoryServer.baseDNs; Backend b = baseDNs.get(entryDN); while (b == null) { backend = directoryServer.privateSuffixes.get(dn); entryDN = entryDN.getParent(); if (entryDN == null) { return null; } while (backend == null) { dn = dn.getParentDNInSuffix(); if (dn == null) { break; b = baseDNs.get(entryDN); } backend = directoryServer.suffixes.get(dn); if (backend == null) { backend = directoryServer.privateSuffixes.get(dn); } } return backend; return b; } /** * Registers the specified suffix to be handled by the provided backend. * Registers the provided base DN with the server. * * @param suffixDN The base DN for this suffix. * @param backend The backend to handle operations for the provided base. * @param baseDN The base DN to register with the server. It must not be * {@code null}. * @param backend The backend responsible for the provided base DN. It * must not be {@code null}. * @param isPrivate Indicates whether the base DN should be considered a * private base DN. If the provided base DN is a naming * context, then this controls whether it is public or * private. * @param testOnly Indicates whether to only test whether the new base DN * registration would be successful without actually * applying any changes. * * @throws ConfigException If the specified suffix is already registered * with the Directory Server. * @throws DirectoryException If a problem occurs while attempting to * register the provided base DN. */ public static void registerSuffix(DN suffixDN, Backend backend) throws ConfigException public static void registerBaseDN(DN baseDN, Backend backend, boolean isPrivate, boolean testOnly) throws DirectoryException { assert debugEnter(CLASS_NAME, "registerSuffix", String.valueOf(suffixDN), String.valueOf(backend)); assert debugEnter(CLASS_NAME, "registerBaseDN", String.valueOf(baseDN), String.valueOf(backend), String.valueOf(isPrivate), String.valueOf(testOnly)); backend.setPrivateBackend(false); ensureNotNull(baseDN, backend); synchronized (directoryServer.suffixes) synchronized (directoryServer) { // Check to see if this suffix is already in use. It may be a suffix, or // it may be a sub-suffix of an existing suffix. Backend b = directoryServer.suffixes.get(suffixDN); if (b != null) { int msgID = MSGID_CANNOT_REGISTER_DUPLICATE_SUFFIX; String message = getMessage(msgID, String.valueOf(suffixDN), b.getClass().getName()); TreeMap<DN,Backend> newBaseDNs = new TreeMap<DN,Backend>(directoryServer.baseDNs); TreeMap<DN,Backend> newPublicNamingContexts = new TreeMap<DN,Backend>(directoryServer.publicNamingContexts); TreeMap<DN,Backend> newPrivateNamingContexts = new TreeMap<DN,Backend>(directoryServer.privateNamingContexts); throw new ConfigException(msgID, message); // Check to see if the base DN is already registered with the server. Backend existingBackend = newBaseDNs.get(baseDN); if (existingBackend != null) { int msgID = MSGID_REGISTER_BASEDN_ALREADY_EXISTS; String message = getMessage(msgID, String.valueOf(baseDN), backend.getBackendID(), existingBackend.getBackendID()); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, msgID); } boolean found = false; DN parentDN = suffixDN.getParentDNInSuffix(); // Check to see if the backend is already registered with the server for // any other base DN(s). The new base DN must not have any hierarchical // relationship with any other base Dns for the same backend. LinkedList<DN> otherBaseDNs = new LinkedList<DN>(); for (DN dn : newBaseDNs.keySet()) { Backend b = newBaseDNs.get(dn); if (b.equals(backend)) { otherBaseDNs.add(dn); if (baseDN.isAncestorOf(dn) || baseDN.isDescendantOf(dn)) { int msgID = MSGID_REGISTER_BASEDN_HIERARCHY_CONFLICT; String message = getMessage(msgID, String.valueOf(baseDN), backend.getBackendID(), String.valueOf(dn)); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, msgID); } } } // Check to see if the new base DN is subordinate to any other base DN // already defined. If it is, then any other base DN(s) for the same // backend must also be subordinate to the same base DN. Backend superiorBackend = null; DN superiorBaseDN = null; DN parentDN = baseDN.getParent(); while (parentDN != null) { b = directoryServer.suffixes.get(suffixDN); if (b != null) if (newBaseDNs.containsKey(parentDN)) { if (b.hasSubSuffix(suffixDN)) { int msgID = MSGID_CANNOT_REGISTER_DUPLICATE_SUBSUFFIX; String message = getMessage(msgID, String.valueOf(suffixDN), String.valueOf(parentDN)); superiorBaseDN = parentDN; superiorBackend = newBaseDNs.get(parentDN); throw new ConfigException(msgID, message); } else for (DN dn : otherBaseDNs) { b.addSubordinateBackend(backend); found = true; if (! dn.isDescendantOf(superiorBaseDN)) { int msgID = MSGID_REGISTER_BASEDN_DIFFERENT_PARENT_BASES; String message = getMessage(msgID, String.valueOf(baseDN), backend.getBackendID(), String.valueOf(dn)); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, msgID); } } break; } parentDN = parentDN.getParent(); } parentDN = parentDN.getParentDNInSuffix(); } if (! found) if (superiorBackend == null) { // If we've gotten here, then it is not in use. Register it. directoryServer.suffixes.put(suffixDN, backend); } // See if there are any supported controls or features that we want to // advertise. Set<String> supportedControls = backend.getSupportedControls(); if (supportedControls != null) if (backend.getParentBackend() != null) { for (String controlOID : supportedControls) int msgID = MSGID_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE; String message = getMessage(msgID, String.valueOf(baseDN), backend.getBackendID(), backend.getParentBackend().getBackendID()); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, msgID); } } // Check to see if the new base DN should be the superior base DN for any // other base DN(s) already defined. LinkedList<Backend> subordinateBackends = new LinkedList<Backend>(); LinkedList<DN> subordinateBaseDNs = new LinkedList<DN>(); for (DN dn : newBaseDNs.keySet()) { registerSupportedControl(controlOID); } } Set<String> supportedFeatures = backend.getSupportedFeatures(); if (supportedFeatures != null) Backend b = newBaseDNs.get(dn); parentDN = dn.getParent(); while (parentDN != null) { for (String featureOID : supportedFeatures) if (parentDN.equals(baseDN)) { registerSupportedFeature(featureOID); subordinateBaseDNs.add(dn); subordinateBackends.add(b); break; } } } } /** * Registers the specified private suffix to be handled by the provided * backend. * * @param suffixDN The base DN for this suffix. * @param backend The backend to handle operations for the provided base. * * @throws ConfigException If the specified suffix is already registered * with the Directory Server. */ public static void registerPrivateSuffix(DN suffixDN, Backend backend) throws ConfigException else if (newBaseDNs.containsKey(parentDN)) { assert debugEnter(CLASS_NAME, "registerPrivateSuffix", String.valueOf(suffixDN), String.valueOf(backend)); break; } parentDN = parentDN.getParent(); } } // If we've gotten here, then the new base DN is acceptable. If we should // actually apply the changes then do so now. if (! testOnly) { // Check to see if any of the registered backends already contain an // entry with the DN specified as the base DN. This could happen if // we're creating a new subordinate backend in an existing directory // (e.g., moving the "ou=People,dc=example,dc=com" branch to its own // backend when that data already exists under the "dc=example,dc=com" // backend). This condition shouldn't prevent the new base DN from // being registered, but it's definitely important enough that we let // the administrator know about it and remind them that the existing // backend will need to be reinitialized. if (superiorBackend != null) { if (superiorBackend.entryExists(baseDN)) { int msgID = MSGID_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS; String message = getMessage(msgID, superiorBackend.getBackendID(), String.valueOf(baseDN), backend.getBackendID()); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } newBaseDNs.put(baseDN, backend); if (superiorBackend == null) { if (isPrivate) { backend.setPrivateBackend(true); synchronized (directoryServer.privateSuffixes) { // Check to see if this suffix is already in use for a "user" suffix. It // may be a suffix, or it may be a sub-suffix of an existing suffix. synchronized (directoryServer.suffixes) { Backend b = directoryServer.suffixes.get(suffixDN); if (b != null) { int msgID = MSGID_CANNOT_REGISTER_DUPLICATE_SUFFIX; String message = getMessage(msgID, String.valueOf(suffixDN), b.getClass().getName()); throw new ConfigException(msgID, message); } DN parentDN = suffixDN.getParentDNInSuffix(); while (parentDN != null) { b = directoryServer.suffixes.get(suffixDN); if (b != null) { int msgID = MSGID_CANNOT_REGISTER_PRIVATE_SUFFIX_BELOW_USER_PARENT; String message = getMessage(msgID, String.valueOf(suffixDN), String.valueOf(parentDN)); throw new ConfigException(msgID, message); } parentDN = suffixDN.getParentDNInSuffix(); } } // Check to see if this suffix is already registered as a private suffix // or sub-suffix. Backend b = directoryServer.privateSuffixes.get(suffixDN); if (b != null) { int msgID = MSGID_CANNOT_REGISTER_DUPLICATE_SUFFIX; String message = getMessage(msgID, String.valueOf(suffixDN), b.getClass().getName()); throw new ConfigException(msgID, message); } DN parentDN = suffixDN.getParentDNInSuffix(); while (parentDN != null) { b = directoryServer.privateSuffixes.get(suffixDN); if (b != null) { if (b.hasSubSuffix(suffixDN)) { int msgID = MSGID_CANNOT_REGISTER_DUPLICATE_SUBSUFFIX; String message = getMessage(msgID, String.valueOf(suffixDN), String.valueOf(parentDN)); throw new ConfigException(msgID, message); newPrivateNamingContexts.put(baseDN, backend); } else { b.addSubordinateBackend(backend); return; backend.setPrivateBackend(false); newPublicNamingContexts.put(baseDN, backend); } } parentDN = suffixDN.getParentDNInSuffix(); else if (otherBaseDNs.isEmpty()) { backend.setParentBackend(superiorBackend); superiorBackend.addSubordinateBackend(backend); } for (Backend b : subordinateBackends) { Backend oldParentBackend = b.getParentBackend(); if (oldParentBackend != null) { oldParentBackend.removeSubordinateBackend(b); } // If we've gotten here, then it is not in use. Register it. directoryServer.privateSuffixes.put(suffixDN, backend); b.setParentBackend(backend); backend.addSubordinateBackend(b); } for (DN dn : subordinateBaseDNs) { newPublicNamingContexts.remove(dn); newPrivateNamingContexts.remove(dn); } directoryServer.baseDNs = newBaseDNs; directoryServer.publicNamingContexts = newPublicNamingContexts; directoryServer.privateNamingContexts = newPrivateNamingContexts; } } } /** * Deregisters the specified suffix with the Directory Server. This should * work regardless of whether the specified DN is a normal suffix or a private * suffix. * Deregisters the provided base DN with the server. * * @param suffixDN The suffix DN to deregister with the server. * @param baseDN The base DN to deregister with the server. It must not * be {@code null}. * @param testOnly Indicates whether to only test whether the new base DN * registration would be successful without actually * applying any changes. * * @throws ConfigException If a problem occurs while attempting to * deregister the specified suffix. * @throws DirectoryException If a problem occurs while attempting to * deregister the provided base DN. */ public static void deregisterSuffix(DN suffixDN) throws ConfigException public static void deregisterBaseDN(DN baseDN, boolean testOnly) throws DirectoryException { assert debugEnter(CLASS_NAME, "deregisterSuffix", String.valueOf(suffixDN)); assert debugEnter(CLASS_NAME, "deregisterBaseDN", String.valueOf(baseDN)); ensureNotNull(baseDN); synchronized (directoryServer) { TreeMap<DN,Backend> newBaseDNs = new TreeMap<DN,Backend>(directoryServer.baseDNs); TreeMap<DN,Backend> newPublicNamingContexts = new TreeMap<DN,Backend>(directoryServer.publicNamingContexts); TreeMap<DN,Backend> newPrivateNamingContexts = new TreeMap<DN,Backend>(directoryServer.privateNamingContexts); // First, check to see if it is a "user" suffix or sub-suffix. synchronized (directoryServer.suffixes) // Make sure that the Directory Server actually contains a backend with // the specified base DN. Backend backend = newBaseDNs.get(baseDN); if (backend == null) { Backend b = directoryServer.suffixes.remove(suffixDN); if (b != null) { return; int msgID = MSGID_DEREGISTER_BASEDN_NOT_REGISTERED; String message = getMessage(msgID, String.valueOf(baseDN)); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, msgID); } DN parentDN = suffixDN.getParentDNInSuffix(); while (parentDN != null) { b = directoryServer.suffixes.get(parentDN); if (b != null) { if (b.hasSubSuffix(suffixDN)) { b.removeSubSuffix(suffixDN, parentDN); } return; // Check to see if the backend has a parent backend, and whether it has // any subordinates with base DNs that are below the base DN to remove. Backend superiorBackend = backend.getParentBackend(); LinkedList<Backend> subordinateBackends = new LinkedList<Backend>(); if (backend.getSubordinateBackends() != null) { for (Backend b : backend.getSubordinateBackends()) { for (DN dn : b.getBaseDNs()) { if (dn.isDescendantOf(baseDN)) { subordinateBackends.add(b); break; } } } } // Check the set of private suffixes and sub-suffixes. synchronized (directoryServer.privateSuffixes) // See if there are any other base DNs registered within the same backend. LinkedList<DN> otherBaseDNs = new LinkedList<DN>(); for (DN dn : newBaseDNs.keySet()) { Backend b = directoryServer.privateSuffixes.remove(suffixDN); if (b != null) if (dn.equals(baseDN)) { return; continue; } DN parentDN = suffixDN.getParentDNInSuffix(); while (parentDN != null) Backend b = newBaseDNs.get(dn); if (backend.equals(b)) { b = directoryServer.privateSuffixes.get(parentDN); if (b != null) { if (b.hasSubSuffix(suffixDN)) { b.removeSubSuffix(suffixDN, parentDN); otherBaseDNs.add(dn); } } return; // If we've gotten here, then it's OK to make the changes. if (! testOnly) { // Get rid of the references to this base DN in the mapping tree // information. newBaseDNs.remove(baseDN); newPublicNamingContexts.remove(baseDN); newPrivateNamingContexts.remove(baseDN); if (superiorBackend == null) { // If there were any subordinate backends, then all of their base DNs // will now be promoted to naming contexts. for (Backend b : subordinateBackends) { b.setParentBackend(null); backend.removeSubordinateBackend(b); for (DN dn : b.getBaseDNs()) { if (b.isPrivateBackend()) { newPrivateNamingContexts.put(dn, b); } else { newPublicNamingContexts.put(dn, b); } } } } else { // If there are no other base DNs for the associated backend, then // remove this backend as a subordinate of the parent backend. if (otherBaseDNs.isEmpty()) { superiorBackend.removeSubordinateBackend(backend); } // If there are any subordinate backends, then they need to be made // subordinate to the parent backend. Also, we should log a warning // message indicating that there may be inconsistent search results // because some of the structural entries will be missing. if (! subordinateBackends.isEmpty()) { int msgID = MSGID_DEREGISTER_BASEDN_MISSING_HIERARCHY; String message = getMessage(msgID, String.valueOf(baseDN), backend.getBackendID()); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_WARNING, message, msgID); for (Backend b : subordinateBackends) { backend.removeSubordinateBackend(b); superiorBackend.addSubordinateBackend(b); b.setParentBackend(superiorBackend); } } } directoryServer.baseDNs = newBaseDNs; directoryServer.publicNamingContexts = newPublicNamingContexts; directoryServer.privateNamingContexts = newPrivateNamingContexts; } } } /** * Retrieves the set of public naming contexts defined in the Directory * Server, mapped from the naming context DN to the corresponding backend. * * @return The set of public naming contexts defined in the Directory Server. */ public static Map<DN,Backend> getPublicNamingContexts() { assert debugEnter(CLASS_NAME, "getPublicNamingContexts"); return directoryServer.publicNamingContexts; } /** * Retrieves the set of private naming contexts defined in the Directory * Server, mapped from the naming context DN to the corresponding backend. * * @return The set of private naming contexts defined in the Directory * Server. */ public static Map<DN,Backend> getPrivateNamingContexts() { assert debugEnter(CLASS_NAME, "getPrivateNamingContexts"); return directoryServer.privateNamingContexts; } /** * Indicates whether the specified DN is one of the Directory Server naming * contexts. * * @param dn The DN for which to make the determination. * * @return {@code true} if the specified DN is a naming context for the * Directory Server, or {@code false} if it is not. */ public static boolean isNamingContext(DN dn) { assert debugEnter(CLASS_NAME, "isNamingContext"); return (directoryServer.publicNamingContexts.containsKey(dn) || directoryServer.privateNamingContexts.containsKey(dn)); } opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -598,7 +598,8 @@ try { DirectoryServer.registerPrivateSuffix(configRootEntry.getDN(), this); DirectoryServer.registerBaseDN(configRootEntry.getDN(), this, true, false); } catch (Exception e) { @@ -729,7 +730,7 @@ try { DirectoryServer.deregisterSuffix(configRootEntry.getDN()); DirectoryServer.deregisterBaseDN(configRootEntry.getDN(), false); } catch (Exception e) { @@ -1796,23 +1797,6 @@ /** * Indicates whether this backend supports the specified control. * * @param controlOID The OID of the control for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * control, or <CODE>false</CODE> */ public boolean supportsControl(String controlOID) { // NYI return false; } /** * Retrieves the OIDs of the features that may be supported by this backend. * * @return The OIDs of the features that may be supported by this backend. @@ -1826,23 +1810,6 @@ /** * Indicates whether this backend supports the specified feature. * * @param featureOID The OID of the feature for which to make the * determination. * * @return <CODE>true</CODE> if this backend does support the requested * feature, or <CODE>false</CODE> */ public boolean supportsFeature(String featureOID) { // NYI return false; } /** * Indicates whether this backend provides a mechanism to export the data it * contains to an LDIF file. * opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -35,9 +35,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -309,7 +309,7 @@ // use it. Otherwise, add a realm for each suffix defined in the server. if (realm == null) { LinkedHashMap<DN,Backend> suffixes = DirectoryServer.getSuffixes(); Map<DN,Backend> suffixes = DirectoryServer.getPublicNamingContexts(); if (! suffixes.isEmpty()) { Iterator<DN> iterator = suffixes.keySet().iterator(); opends/src/server/org/opends/server/messages/BackendMessages.java
@@ -2233,6 +2233,17 @@ /** * The message ID for the message that will be used if an error occurs while * attempting to register a base DN for use in the server. This takes two * arguments, which are the base DN and a string representation of the * exception that was caught. */ public static final int MSGID_BACKEND_CANNOT_REGISTER_BASEDN = CATEGORY_MASK_BACKEND | SEVERITY_MASK_FATAL_ERROR | 210; /** * Associates a set of generic messages with the message IDs defined in this * class. */ @@ -2252,6 +2263,9 @@ "the entry is already locked by a long-running operation " + "or that the entry has previously been locked but was " + "not properly unlocked."); registerMessage(MSGID_BACKEND_CANNOT_REGISTER_BASEDN, "An error occurred while attempting to register base DN " + "in the Directory Server: %s."); registerMessage(MSGID_ROOTDSE_CONFIG_ENTRY_NULL, opends/src/server/org/opends/server/messages/ConfigMessages.java
@@ -6153,6 +6153,17 @@ /** * The message ID for the message that will be used if an error occurs while * attempting to register a backend with the Directory Server. This takes two * arguments, which are the backend ID and a string representation of the * exception that was caught. */ public static final int MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND = CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_WARNING | 572; /** * Associates a set of generic messages with the message IDs defined in this * class. */ @@ -7020,6 +7031,9 @@ "lock for backend %s: %s. This may interfere with " + "operations that require exclusive access, including " + "LDIF import and restoring a backup."); registerMessage(MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND, "An error occurred while attempting to register backend " + "%s with the Directory Server: %s."); registerMessage(MSGID_CONFIG_BACKEND_CLASS_NOT_BACKEND, "The class %s specified in configuration entry %s does " + "not contain a valid Directory Server backend " + opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -5967,6 +5967,98 @@ /** * The message ID for the message that will be used if an attempt is made to * register a backend with an ID that is already registered. This takes a * single argument, which is the conflicting backend ID. */ public static final int MSGID_REGISTER_BACKEND_ALREADY_EXISTS = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 571; /** * The message ID for the message that will be used if an attempt is made to * register a base DN that is already registered. This takes three arguments, * which are the base DN, the backend ID for the new backend, and the backend * ID for the already-registered backend. */ public static final int MSGID_REGISTER_BASEDN_ALREADY_EXISTS = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 572; /** * The message ID for the message that will be used if an attempt is made to * register a base DN with a backend that already has another base DN that * contains conflicting hierarchy. This takes three arguments, which are the * base DN to be registered, the backend ID, and the already-registered base * DN. */ public static final int MSGID_REGISTER_BASEDN_HIERARCHY_CONFLICT = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 573; /** * The message ID for the message that will be used if an attempt is made to * register a base DN with a backend that already contains other base DNs * that do not share the same parent base DN. This takes three arguments, * which are the base DN to be registered, the backend ID, and the * already-registered base DN. */ public static final int MSGID_REGISTER_BASEDN_DIFFERENT_PARENT_BASES = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 574; /** * The message ID for the message that will be used if an attempt is made to * register a base DN with a backend that already contains other base DNs * that are subordinate to a backend whereas the new base DN is not * subordinate to any backend. This takes three arguments, which are the base * DN to be registered, the backend ID, and the backend ID of the parent * backend. */ public static final int MSGID_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 575; /** * The message ID for the message that will be used if a newly-registered * backend has a base DN for which a corresponding entry already exists in the * server. This takes three arguments, which are the backend ID for the * existing backend, the base DN for the new backend, and the backend ID for * the new backend. */ public static final int MSGID_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 576; /** * The message ID for the message that will be used if an attempt is made to * deregister a base DN that is not registered. This takes a single argument, * which is the base DN to deregister. */ public static final int MSGID_DEREGISTER_BASEDN_NOT_REGISTERED = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 577; /** * The message ID for the message that will be used if a base DN containing * both superior and subordinate backends is deregistered, leaving the * possibility for missing entries in the data hierarchy. This takes two * arguments, which are the base DN that has been deregistered and the backend * ID of the associated backend. */ public static final int MSGID_DEREGISTER_BASEDN_MISSING_HIERARCHY = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 578; /** * Associates a set of generic messages with the message IDs defined * in this class. */ @@ -8057,6 +8149,50 @@ "The user-specific lookthrough limit value %s contained " + "in user entry %s could not be parsed as an integer. " + "The default server lookthrough limit will be used."); registerMessage(MSGID_REGISTER_BACKEND_ALREADY_EXISTS, "Unable to register backend %s with the Directory Server " + "because another backend with the same backend ID is " + "already registered."); registerMessage(MSGID_REGISTER_BASEDN_ALREADY_EXISTS, "Unable to register base DN %s with the Directory Server " + "for backend %s because that base DN is already " + "registered for backend %s."); registerMessage(MSGID_REGISTER_BASEDN_HIERARCHY_CONFLICT, "Unable to register base DN %s with the Directory Server " + "for backend %s because that backend already contains " + "another base DN %s that is within the same hierarchical " + "path."); registerMessage(MSGID_REGISTER_BASEDN_DIFFERENT_PARENT_BASES, "Unable to register base DN %s with the Directory Server " + "for backend %s because that backend already contains " + "another base DN %s that is not subordinate to the same " + "base DN in the parent backend."); registerMessage(MSGID_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE, "Unable to register base DN %s with the Directory Server " + "for backend %s because that backend already contains " + "one or more other base DNs that are subordinate to " + "backend %s but the new base DN is not."); registerMessage(MSGID_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS, "Backend %s already contains entry %s which has just " + "been registered as the base DN for backend %s. " + "These conflicting entries may cause unexpected or " + "errant search results, and both backends should be " + "reinitialized to ensure that each has the correct " + "content."); registerMessage(MSGID_DEREGISTER_BASEDN_NOT_REGISTERED, "Unable to de-register base DN %s with the Directory " + "Server because that base DN is not registered for any " + "active backend."); registerMessage(MSGID_DEREGISTER_BASEDN_MISSING_HIERARCHY, "Base DN %s has been deregistered from the Directory " + "Server for backend %s. This base DN had both superior " + "and subordinate entries in other backends, and there " + "may be inconsistent or unexpected behavior when " + "accessing entries in this portion of the hierarchy " + "because of the missing entries that had been held in " + "the de-registered backend."); } } opends/src/server/org/opends/server/types/DN.java
@@ -507,7 +507,9 @@ public DN getParentDNInSuffix() { assert debugEnter(CLASS_NAME, "getParentDNInSuffix"); if ((numComponents <= 1) || (DirectoryServer.isSuffix(this))) { if ((numComponents <= 1) || DirectoryServer.isNamingContext(this)) { return null; } opends/src/server/org/opends/server/util/ServerConstants.java
@@ -186,8 +186,8 @@ /** * The name of the standard attribute that is used to specify the set of * naming contexts (suffixes) for the Directory Server, formatted in camel * case. * public naming contexts (suffixes) for the Directory Server, formatted in * camel case. */ public static final String ATTR_NAMING_CONTEXTS = "namingContexts"; @@ -195,14 +195,23 @@ /** * The name of the standard attribute that is used to specify the set of * naming contexts (suffixes) for the Directory Server, formatted in all * lowercase. * public naming contexts (suffixes) for the Directory Server, formatted in * all lowercase. */ public static final String ATTR_NAMING_CONTEXTS_LC = "namingcontexts"; /** * The name of the attribute used to hold the DNs that constitute the set of * "private" naming contexts registered with the server. */ public static final String ATTR_PRIVATE_NAMING_CONTEXTS = "ds-private-naming-contexts"; /** * The name of the standard attribute that is used to hold organization names, * formatted in all lowercase. */ opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -348,6 +348,7 @@ if (memoryBackend == null) { memoryBackend = new MemoryBackend(); memoryBackend.setBackendID("test"); memoryBackend.initializeBackend(null, new DN[] { baseDN }); DirectoryServer.registerBackend(memoryBackend); } opends/tests/unit-tests-testng/src/server/org/opends/server/core/BackendConfigManagerTestCase.java
New file @@ -0,0 +1,783 @@ /* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 * * * Portions Copyright 2006 Sun Microsystems, Inc. */ package org.opends.server.core; import java.util.ArrayList; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import org.opends.server.TestCaseUtils; import org.opends.server.api.Backend; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.types.Attribute; import org.opends.server.types.DirectoryException; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.Modification; import org.opends.server.types.ModificationType; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchFilter; import org.opends.server.types.SearchScope; import static org.testng.Assert.*; import static org.opends.server.util.StaticUtils.*; /** * A set of generic test cases that cover adding, modifying, and removing * Directory Server backends. */ public class BackendConfigManagerTestCase extends CoreTestCase { /** * Ensures that the Directory Server is running. * * @throws Exception If an unexpected problem occurs. */ @BeforeClass() public void startServer() throws Exception { TestCaseUtils.startServer(); } /** * Tests that the server will reject an attempt to register a base DN that is * already defined in the server. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testRegisterBaseThatAlreadyExists() throws Exception { TestCaseUtils.initializeTestBackend(false); DN baseDN = DN.decode("o=test"); String backendID = createBackendID(baseDN); Entry backendEntry = createBackendEntry(backendID, false, baseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(), backendEntry.getUserAttributes(), backendEntry.getOperationalAttributes()); assertFalse(addOperation.getResultCode() == ResultCode.SUCCESS); } /** * Tests that the server will reject an attempt to deregister a base DN that * is not defined in the server. * * @throws Exception If an unexpected problem occurs. */ @Test(expectedExceptions = { DirectoryException.class }) public void testDeregisterNonExistentBaseDN() throws Exception { DirectoryServer.deregisterBaseDN(DN.decode("o=unregistered"), false); } /** * Tests that the server will reject an attempt to register a base DN using a * backend with a backend ID that is already defined in the server. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testRegisterBackendIDThatAlreadyExists() throws Exception { TestCaseUtils.initializeTestBackend(false); DN baseDN = DN.decode("o=test"); String backendID = "test"; Entry backendEntry = createBackendEntry(backendID, false, baseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(), backendEntry.getUserAttributes(), backendEntry.getOperationalAttributes()); assertFalse(addOperation.getResultCode() == ResultCode.SUCCESS); } /** * Tests the ability of the server to create and remove a backend that is * never enabled. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testAddAndRemoveDisabledBackend() throws Exception { DN baseDN = DN.decode("o=bcmtest"); String backendID = createBackendID(baseDN); Entry backendEntry = createBackendEntry(backendID, false, baseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(), backendEntry.getUserAttributes(), backendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(backendID)); assertNull(DirectoryServer.getBackendWithBaseDN(baseDN)); DeleteOperation deleteOperation = conn.processDelete(backendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); } /** * Tests the ability of the server to create and remove a backend that is * enabled. It will also test the ability of that backend to hold entries. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testAddAndRemoveEnabledBackend() throws Exception { DN baseDN = DN.decode("o=bcmtest"); String backendID = createBackendID(baseDN); Entry backendEntry = createBackendEntry(backendID, true, baseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(), backendEntry.getUserAttributes(), backendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend backend = DirectoryServer.getBackend(backendID); assertNotNull(backend); assertEquals(backend, DirectoryServer.getBackendWithBaseDN(baseDN)); assertNull(backend.getParentBackend()); assertTrue(backend.getSubordinateBackends().length == 0); assertFalse(backend.entryExists(baseDN)); assertTrue(DirectoryServer.isNamingContext(baseDN)); Entry e = createEntry(baseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(backend.entryExists(baseDN)); DeleteOperation deleteOperation = conn.processDelete(backendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(backendID)); } /** * Tests the ability of the server to create a backend that is disabled and * then enable it through a configuration change, and then subsequently * disable it. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testEnableAndDisableBackend() throws Exception { // Create the backend and make it disabled. DN baseDN = DN.decode("o=bcmtest"); String backendID = createBackendID(baseDN); Entry backendEntry = createBackendEntry(backendID, false, baseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(), backendEntry.getUserAttributes(), backendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(backendID)); assertFalse(DirectoryServer.isNamingContext(baseDN)); // Modify the backend to enable it. ArrayList<Modification> mods = new ArrayList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, new Attribute("ds-cfg-backend-enabled", "true"))); ModifyOperation modifyOperation = conn.processModify(backendEntry.getDN(), mods); assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS); Backend backend = DirectoryServer.getBackend(backendID); assertNotNull(backend); assertEquals(backend, DirectoryServer.getBackendWithBaseDN(baseDN)); assertNull(backend.getParentBackend()); assertTrue(backend.getSubordinateBackends().length == 0); assertFalse(backend.entryExists(baseDN)); assertTrue(DirectoryServer.isNamingContext(baseDN)); Entry e = createEntry(baseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(backend.entryExists(baseDN)); // Modify the backend to disable it. mods = new ArrayList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, new Attribute("ds-cfg-backend-enabled", "false"))); modifyOperation = conn.processModify(backendEntry.getDN(), mods); assertNull(DirectoryServer.getBackend(backendID)); assertFalse(DirectoryServer.entryExists(baseDN)); assertFalse(DirectoryServer.isNamingContext(baseDN)); // Delete the disabled backend. DeleteOperation deleteOperation = conn.processDelete(backendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); } /** * Tests the ability of the Directory Server to work properly when adding * nested backends in which the parent is added first and the child second. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testAddNestedBackendParentFirst() throws Exception { // Create the parent backend and the corresponding base entry. DN parentBaseDN = DN.decode("o=parent"); String parentBackendID = createBackendID(parentBaseDN); Entry parentBackendEntry = createBackendEntry(parentBackendID, true, parentBaseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(parentBackendEntry.getDN(), parentBackendEntry.getObjectClasses(), parentBackendEntry.getUserAttributes(), parentBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend parentBackend = DirectoryServer.getBackend(parentBackendID); assertNotNull(parentBackend); assertEquals(parentBackend, DirectoryServer.getBackendWithBaseDN(parentBaseDN)); assertNull(parentBackend.getParentBackend()); assertTrue(parentBackend.getSubordinateBackends().length == 0); assertFalse(parentBackend.entryExists(parentBaseDN)); assertTrue(DirectoryServer.isNamingContext(parentBaseDN)); Entry e = createEntry(parentBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(parentBackend.entryExists(parentBaseDN)); // Create the child backend and the corresponding base entry. DN childBaseDN = DN.decode("ou=child,o=parent"); String childBackendID = createBackendID(childBaseDN); Entry childBackendEntry = createBackendEntry(childBackendID, true, childBaseDN); addOperation = conn.processAdd(childBackendEntry.getDN(), childBackendEntry.getObjectClasses(), childBackendEntry.getUserAttributes(), childBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend childBackend = DirectoryServer.getBackend(childBackendID); assertNotNull(childBackend); assertEquals(childBackend, DirectoryServer.getBackendWithBaseDN(childBaseDN)); assertNotNull(childBackend.getParentBackend()); assertEquals(parentBackend, childBackend.getParentBackend()); assertTrue(parentBackend.getSubordinateBackends().length == 1); assertFalse(childBackend.entryExists(childBaseDN)); assertFalse(DirectoryServer.isNamingContext(childBaseDN)); e = createEntry(childBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(childBackend.entryExists(childBaseDN)); // Make sure that both entries exist. InternalSearchOperation internalSearch = conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE, SearchFilter.createFilterFromString("(objectClass=*)")); assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS); assertEquals(internalSearch.getSearchEntries().size(), 2); // Make sure that we can't remove the parent backend with the child still // in place. DeleteOperation deleteOperation = conn.processDelete(parentBackendEntry.getDN()); assertFalse(deleteOperation.getResultCode() == ResultCode.SUCCESS); assertNotNull(DirectoryServer.getBackend(parentBackendID)); // Delete the child and then delete the parent. deleteOperation = conn.processDelete(childBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(childBackendID)); assertTrue(parentBackend.getSubordinateBackends().length == 0); deleteOperation = conn.processDelete(parentBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(parentBackendID)); } /** * Tests the ability of the Directory Server to work properly when adding * nested backends in which the child is added first and the parent second. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testAddNestedBackendChildFirst() throws Exception { // Create the child backend and the corresponding base entry (at the time // of the creation, it will be a naming context). DN childBaseDN = DN.decode("ou=child,o=parent"); String childBackendID = createBackendID(childBaseDN); Entry childBackendEntry = createBackendEntry(childBackendID, true, childBaseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(childBackendEntry.getDN(), childBackendEntry.getObjectClasses(), childBackendEntry.getUserAttributes(), childBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend childBackend = DirectoryServer.getBackend(childBackendID); assertNotNull(childBackend); assertEquals(childBackend, DirectoryServer.getBackendWithBaseDN(childBaseDN)); assertFalse(childBackend.entryExists(childBaseDN)); assertNull(childBackend.getParentBackend()); assertTrue(childBackend.getSubordinateBackends().length == 0); assertFalse(childBackend.entryExists(childBaseDN)); Entry e = createEntry(childBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(childBackend.entryExists(childBaseDN)); assertTrue(DirectoryServer.isNamingContext(childBaseDN)); // Create the parent backend and the corresponding entry (and verify that // its DN is now a naming context and the child's is not). DN parentBaseDN = DN.decode("o=parent"); String parentBackendID = createBackendID(parentBaseDN); Entry parentBackendEntry = createBackendEntry(parentBackendID, true, parentBaseDN); addOperation = conn.processAdd(parentBackendEntry.getDN(), parentBackendEntry.getObjectClasses(), parentBackendEntry.getUserAttributes(), parentBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend parentBackend = DirectoryServer.getBackend(parentBackendID); assertNotNull(parentBackend); assertEquals(parentBackend, DirectoryServer.getBackendWithBaseDN(parentBaseDN)); assertNotNull(childBackend.getParentBackend()); assertEquals(parentBackend, childBackend.getParentBackend()); assertTrue(parentBackend.getSubordinateBackends().length == 1); e = createEntry(parentBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(parentBackend.entryExists(parentBaseDN)); assertTrue(DirectoryServer.isNamingContext(parentBaseDN)); assertFalse(DirectoryServer.isNamingContext(childBaseDN)); // Verify that we can see both entries with a subtree search. InternalSearchOperation internalSearch = conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE, SearchFilter.createFilterFromString("(objectClass=*)")); assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS); assertEquals(internalSearch.getSearchEntries().size(), 2); // Delete the backends from the server. DeleteOperation deleteOperation = conn.processDelete(childBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(childBackendID)); assertTrue(parentBackend.getSubordinateBackends().length == 0); deleteOperation = conn.processDelete(parentBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(parentBackendID)); } /** * Tests the ability of the Directory Server to work properly when inserting * an intermediate backend between a parent backend and an existing nested * backend. * * @throws Exception If an unexpected problem occurs. */ @Test() public void testInsertIntermediateBackend() throws Exception { // Add the parent backend to the server and its corresponding base entry. DN parentBaseDN = DN.decode("o=parent"); String parentBackendID = createBackendID(parentBaseDN); Entry parentBackendEntry = createBackendEntry(parentBackendID, true, parentBaseDN); InternalClientConnection conn = InternalClientConnection.getRootConnection(); AddOperation addOperation = conn.processAdd(parentBackendEntry.getDN(), parentBackendEntry.getObjectClasses(), parentBackendEntry.getUserAttributes(), parentBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend parentBackend = DirectoryServer.getBackend(parentBackendID); assertNotNull(parentBackend); assertEquals(parentBackend, DirectoryServer.getBackendWithBaseDN(parentBaseDN)); assertNull(parentBackend.getParentBackend()); assertTrue(parentBackend.getSubordinateBackends().length == 0); assertFalse(parentBackend.entryExists(parentBaseDN)); Entry e = createEntry(parentBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(parentBackend.entryExists(parentBaseDN)); assertTrue(DirectoryServer.isNamingContext(parentBaseDN)); // Add the grandchild backend to the server. DN grandchildBaseDN = DN.decode("ou=grandchild,ou=child,o=parent"); String grandchildBackendID = createBackendID(grandchildBaseDN); Entry grandchildBackendEntry = createBackendEntry(grandchildBackendID, true, grandchildBaseDN); addOperation = conn.processAdd(grandchildBackendEntry.getDN(), grandchildBackendEntry.getObjectClasses(), grandchildBackendEntry.getUserAttributes(), grandchildBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend grandchildBackend = DirectoryServer.getBackend(grandchildBackendID); assertNotNull(grandchildBackend); assertEquals(grandchildBackend, DirectoryServer.getBackendWithBaseDN(grandchildBaseDN)); assertNotNull(grandchildBackend.getParentBackend()); assertEquals(grandchildBackend.getParentBackend(), parentBackend); assertTrue(parentBackend.getSubordinateBackends().length == 1); assertFalse(grandchildBackend.entryExists(grandchildBaseDN)); // Verify that we can't create the grandchild base entry because its parent // doesn't exist. e = createEntry(grandchildBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.NO_SUCH_OBJECT); assertFalse(grandchildBackend.entryExists(grandchildBaseDN)); // Add the child backend to the server and create its base entry. DN childBaseDN = DN.decode("ou=child,o=parent"); String childBackendID = createBackendID(childBaseDN); Entry childBackendEntry = createBackendEntry(childBackendID, true, childBaseDN); addOperation = conn.processAdd(childBackendEntry.getDN(), childBackendEntry.getObjectClasses(), childBackendEntry.getUserAttributes(), childBackendEntry.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); Backend childBackend = DirectoryServer.getBackend(childBackendID); assertNotNull(childBackend); assertEquals(childBackend, DirectoryServer.getBackendWithBaseDN(childBaseDN)); assertNotNull(childBackend.getParentBackend()); assertEquals(parentBackend, childBackend.getParentBackend()); assertTrue(parentBackend.getSubordinateBackends().length == 1); assertFalse(childBackend.entryExists(childBaseDN)); assertTrue(childBackend.getSubordinateBackends().length == 1); assertEquals(childBackend.getSubordinateBackends()[0], grandchildBackend); assertEquals(grandchildBackend.getParentBackend(), childBackend); e = createEntry(childBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(childBackend.entryExists(childBaseDN)); // Now we can create the grandchild base entry. e = createEntry(grandchildBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(grandchildBackend.entryExists(grandchildBaseDN)); // Verify that a subtree search can see all three entries. InternalSearchOperation internalSearch = conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE, SearchFilter.createFilterFromString("(objectClass=*)")); assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS); assertEquals(internalSearch.getSearchEntries().size(), 3); // Disable the intermediate (child) backend. This should be allowed. ArrayList<Modification> mods = new ArrayList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, new Attribute("ds-cfg-backend-enabled", "false"))); ModifyOperation modifyOperation = conn.processModify(childBackendEntry.getDN(), mods); assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS); // Make sure that we now only see two entries with the subtree search // (and those two entries should be the parent and grandchild base entries). internalSearch = conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE, SearchFilter.createFilterFromString("(objectClass=*)")); assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS); assertEquals(internalSearch.getSearchEntries().size(), 2); // Re-enable the intermediate backend. mods = new ArrayList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, new Attribute("ds-cfg-backend-enabled", "true"))); modifyOperation = conn.processModify(childBackendEntry.getDN(), mods); assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS); // Update our reference to the child backend since the old one is no longer // valid, and make sure that it got re-inserted back into the same place in // the hierarchy. childBackend = DirectoryServer.getBackend(childBackendID); assertNotNull(childBackend); assertEquals(childBackend, DirectoryServer.getBackendWithBaseDN(childBaseDN)); assertNotNull(childBackend.getParentBackend()); assertEquals(parentBackend, childBackend.getParentBackend()); assertTrue(parentBackend.getSubordinateBackends().length == 1); assertFalse(childBackend.entryExists(childBaseDN)); assertTrue(childBackend.getSubordinateBackends().length == 1); assertEquals(childBackend.getSubordinateBackends()[0], grandchildBackend); assertEquals(grandchildBackend.getParentBackend(), childBackend); // Since the memory backend that we're using for this test doesn't retain // entries across stops and restarts, a subtree search below the parent // should still only return two entries, which means that it's going through // the entire chain of backends. internalSearch = conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE, SearchFilter.createFilterFromString("(objectClass=*)")); assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS); assertEquals(internalSearch.getSearchEntries().size(), 2); // Add the child entry back into the server to get things back to the way // they were before we disabled the backend. e = createEntry(childBaseDN); addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), e.getOperationalAttributes()); assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); assertTrue(childBackend.entryExists(childBaseDN)); // We should again be able to see all three entries when performing a // search. internalSearch = conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE, SearchFilter.createFilterFromString("(objectClass=*)")); assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS); assertEquals(internalSearch.getSearchEntries().size(), 3); // Get rid of the entries in the proper order. DeleteOperation deleteOperation = conn.processDelete(grandchildBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(grandchildBackendID)); assertTrue(childBackend.getSubordinateBackends().length == 0); assertTrue(parentBackend.getSubordinateBackends().length == 1); deleteOperation = conn.processDelete(childBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(childBackendID)); assertTrue(parentBackend.getSubordinateBackends().length == 0); deleteOperation = conn.processDelete(parentBackendEntry.getDN()); assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS); assertNull(DirectoryServer.getBackend(parentBackendID)); } /** * Creates an entry that may be used to add a new backend to the server. It * will be an instance of the memory backend. * * @param backendID The backend ID to use for the backend. * @param enabled Indicates whether the backend should be enabled. * @param baseDNs The set of base DNs to use for the new backend. * * @return An entry that may be used to add a new backend to the server. * * @throws Exception If an unexpected problem occurs. */ private Entry createBackendEntry(String backendID, boolean enabled, DN... baseDNs) throws Exception { assertNotNull(baseDNs); assertFalse(baseDNs.length == 0); ArrayList<String> lines = new ArrayList<String>(); lines.add("dn: ds-cfg-backend-id=" + backendID + ",cn=Backends,cn=config"); lines.add("objectClass: top"); lines.add("objectClass: ds-cfg-backend"); lines.add("ds-cfg-backend-id: " + backendID); lines.add("ds-cfg-backend-class: org.opends.server.backends.MemoryBackend"); lines.add("ds-cfg-backend-enabled: " + String.valueOf(enabled)); lines.add("ds-cfg-backend-writability-mode: enabled"); for (DN dn : baseDNs) { lines.add("ds-cfg-backend-base-dn: " + dn.toString()); } String[] lineArray = new String[lines.size()]; lines.toArray(lineArray); return TestCaseUtils.makeEntry(lineArray); } /** * Constructs a backend ID to use for a backend with the provided set of base * DNs. * * @param baseDNs The set of base DNs to use when creating the backend ID. * * @return The constructed backend ID based on the given base DNs. */ private String createBackendID(DN... baseDNs) { StringBuilder buffer = new StringBuilder(); for (DN dn : baseDNs) { if (buffer.length() > 0) { buffer.append("___"); } String ndn = dn.toNormalizedString(); for (int i=0; i < ndn.length(); i++) { char c = ndn.charAt(i); if (Character.isLetterOrDigit(c)) { buffer.append(c); } else { buffer.append('_'); } } } return buffer.toString(); } }