/*
* 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
*
*
* Copyright 2007-2008 Sun Microsystems, Inc.
*/
package org.opends.server.core;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
import org.opends.server.api.Backend;
import static org.opends.server.util.Validator.ensureNotNull;
import org.opends.messages.Message;
import static org.opends.messages.CoreMessages.*;
import java.util.TreeMap;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
/**
* Registry for maintaining the set of registered base DN's, assocated
* backends and naming context information.
*/
public class BaseDnRegistry {
// 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;
// Indicates whether or not this base DN registry is in test mode.
// A registry instance that is in test mode will not modify backend
// objects referred to in the above maps.
private boolean testOnly;
/**
* Registers a base DN with this registry.
*
* @param baseDN to register
* @param backend with which the base DN is assocated
* @param isPrivate indicates whether or not this base DN is private
* @return list of error messages generated by registering the base DN
* that should be logged if the changes to this registry are
* committed to the server
* @throws DirectoryException if the base DN cannot be registered
*/
public List<Message> registerBaseDN(DN baseDN, Backend backend,
boolean isPrivate)
throws DirectoryException
{
List<Message> errors = new LinkedList<Message>();
// Check to see if the base DN is already registered with the server.
Backend existingBackend = baseDNs.get(baseDN);
if (existingBackend != null)
{
Message message = ERR_REGISTER_BASEDN_ALREADY_EXISTS.
get(String.valueOf(baseDN), backend.getBackendID(),
existingBackend.getBackendID());
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
// 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 : baseDNs.keySet())
{
Backend b = baseDNs.get(dn);
if (b.equals(backend))
{
otherBaseDNs.add(dn);
if (baseDN.isAncestorOf(dn) || baseDN.isDescendantOf(dn))
{
Message message = ERR_REGISTER_BASEDN_HIERARCHY_CONFLICT.
get(String.valueOf(baseDN), backend.getBackendID(),
String.valueOf(dn));
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
message);
}
}
}
// 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 ;
DN parentDN = baseDN.getParent();
while (parentDN != null)
{
if (baseDNs.containsKey(parentDN))
{
superiorBaseDN = parentDN;
superiorBackend = baseDNs.get(parentDN);
for (DN dn : otherBaseDNs)
{
if (! dn.isDescendantOf(superiorBaseDN))
{
Message message = ERR_REGISTER_BASEDN_DIFFERENT_PARENT_BASES.
get(String.valueOf(baseDN), backend.getBackendID(),
String.valueOf(dn));
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
message);
}
}
break;
}
parentDN = parentDN.getParent();
}
if (superiorBackend == null)
{
if (backend.getParentBackend() != null)
{
Message message = ERR_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE.
get(String.valueOf(baseDN), backend.getBackendID(),
backend.getParentBackend().getBackendID());
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
message);
}
}
// 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 : baseDNs.keySet())
{
Backend b = baseDNs.get(dn);
parentDN = dn.getParent();
while (parentDN != null)
{
if (parentDN.equals(baseDN))
{
subordinateBaseDNs.add(dn);
subordinateBackends.add(b);
break;
}
else if (baseDNs.containsKey(parentDN))
{
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.
// 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))
{
Message message = WARN_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS.
get(superiorBackend.getBackendID(), String.valueOf(baseDN),
backend.getBackendID());
errors.add(message);
}
}
baseDNs.put(baseDN, backend);
if (superiorBackend == null)
{
if (isPrivate)
{
if (!testOnly)
{
backend.setPrivateBackend(true);
}
privateNamingContexts.put(baseDN, backend);
}
else
{
if (!testOnly)
{
backend.setPrivateBackend(false);
}
publicNamingContexts.put(baseDN, backend);
}
}
else if (otherBaseDNs.isEmpty())
{
if (!testOnly)
{
backend.setParentBackend(superiorBackend);
superiorBackend.addSubordinateBackend(backend);
}
}
if (!testOnly)
{
for (Backend b : subordinateBackends)
{
Backend oldParentBackend = b.getParentBackend();
if (oldParentBackend != null)
{
oldParentBackend.removeSubordinateBackend(b);
}
b.setParentBackend(backend);
backend.addSubordinateBackend(b);
}
}
for (DN dn : subordinateBaseDNs)
{
publicNamingContexts.remove(dn);
privateNamingContexts.remove(dn);
}
return errors;
}
/**
* Deregisters a base DN with this registry.
*
* @param baseDN to deregister
* @return list of error messages generated by deregistering the base DN
* that should be logged if the changes to this registry are
* committed to the server
* @throws DirectoryException if the base DN could not be deregistered
*/
public List<Message> deregisterBaseDN(DN baseDN)
throws DirectoryException
{
LinkedList<Message> errors = new LinkedList<Message>();
ensureNotNull(baseDN);
// Make sure that the Directory Server actually contains a backend with
// the specified base DN.
Backend backend = baseDNs.get(baseDN);
if (backend == null)
{
Message message =
ERR_DEREGISTER_BASEDN_NOT_REGISTERED.get(String.valueOf(baseDN));
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
// 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;
}
}
}
}
// See if there are any other base DNs registered within the same backend.
LinkedList<DN> otherBaseDNs = new LinkedList<DN>();
for (DN dn : baseDNs.keySet())
{
if (dn.equals(baseDN))
{
continue;
}
Backend b = baseDNs.get(dn);
if (backend.equals(b))
{
otherBaseDNs.add(dn);
}
}
// If we've gotten here, then it's OK to make the changes.
// Get rid of the references to this base DN in the mapping tree
// information.
baseDNs.remove(baseDN);
publicNamingContexts.remove(baseDN);
privateNamingContexts.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)
{
if (!testOnly)
{
b.setParentBackend(null);
backend.removeSubordinateBackend(b);
}
for (DN dn : b.getBaseDNs())
{
if (b.isPrivateBackend())
{
privateNamingContexts.put(dn, b);
}
else
{
publicNamingContexts.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())
{
if (!testOnly)
{
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())
{
// Suppress this warning message on server shutdown.
if (!DirectoryServer.getInstance().isShuttingDown()) {
Message message = WARN_DEREGISTER_BASEDN_MISSING_HIERARCHY.get(
String.valueOf(baseDN), backend.getBackendID());
errors.add(message);
}
if (!testOnly)
{
for (Backend b : subordinateBackends)
{
backend.removeSubordinateBackend(b);
superiorBackend.addSubordinateBackend(b);
b.setParentBackend(superiorBackend);
}
}
}
}
return errors;
}
/**
* Creates a default instance.
*/
BaseDnRegistry()
{
this(new TreeMap<DN,Backend>(), new TreeMap<DN,Backend>(),
new TreeMap<DN,Backend>(), false);
}
/**
* Returns a copy of this registry.
*
* @return copy of this registry
*/
BaseDnRegistry copy()
{
return new BaseDnRegistry(
new TreeMap<DN,Backend>(baseDNs),
new TreeMap<DN,Backend>(publicNamingContexts),
new TreeMap<DN,Backend>(privateNamingContexts),
true);
}
/**
* Creates a parameterized instance.
*
* @param baseDNs map
* @param publicNamingContexts map
* @param privateNamingContexts map
* @param testOnly indicates whether this registry will be used for testing;
* when <code>true</code> this registry will not modify backends
*/
private BaseDnRegistry(TreeMap<DN, Backend> baseDNs,
TreeMap<DN, Backend> publicNamingContexts,
TreeMap<DN, Backend> privateNamingContexts,
boolean testOnly)
{
this.baseDNs = baseDNs;
this.publicNamingContexts = publicNamingContexts;
this.privateNamingContexts = privateNamingContexts;
this.testOnly = testOnly;
}
/**
* Gets the mapping of registered base DNs to their associated backend.
*
* @return mapping from base DN to backend
*/
Map<DN,Backend> getBaseDnMap() {
return this.baseDNs;
}
/**
* Gets the mapping of registered public naming contexts to their
* associated backend.
*
* @return mapping from naming context to backend
*/
Map<DN,Backend> getPublicNamingContextsMap() {
return this.publicNamingContexts;
}
/**
* Gets the mapping of registered private naming contexts to their
* associated backend.
*
* @return mapping from naming context to backend
*/
Map<DN,Backend> getPrivateNamingContextsMap() {
return this.privateNamingContexts;
}
/**
* Indicates whether the specified DN is contained in this registry as
* a naming contexts.
*
* @param dn The DN for which to make the determination.
*
* @return {@code true} if the specified DN is a naming context in this
* registry, or {@code false} if it is not.
*/
boolean containsNamingContext(DN dn)
{
return (privateNamingContexts.containsKey(dn) ||
publicNamingContexts.containsKey(dn));
}
/**
* Clear and nullify this registry's internal state.
*/
void clear() {
if (baseDNs != null)
{
baseDNs.clear();
}
if (privateNamingContexts != null)
{
privateNamingContexts.clear();
}
if (publicNamingContexts != null)
{
publicNamingContexts.clear();
}
}
}