/* * 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(); } } }