001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2007-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Map; 032import java.util.TreeMap; 033 034import org.forgerock.i18n.LocalizableMessage; 035import org.forgerock.opendj.ldap.ResultCode; 036import org.opends.server.api.Backend; 037import org.opends.server.types.DN; 038import org.opends.server.types.DirectoryException; 039 040import static org.forgerock.util.Reject.*; 041import static org.opends.messages.CoreMessages.*; 042 043/** 044 * Registry for maintaining the set of registered base DN's, associated backends 045 * and naming context information. 046 */ 047public class BaseDnRegistry { 048 049 /** The set of base DNs registered with the server. */ 050 private final TreeMap<DN, Backend> baseDNs = new TreeMap<>(); 051 /** The set of private naming contexts registered with the server. */ 052 private final TreeMap<DN, Backend> privateNamingContexts = new TreeMap<>(); 053 /** The set of public naming contexts registered with the server. */ 054 private final TreeMap<DN, Backend> publicNamingContexts = new TreeMap<>(); 055 056 /** 057 * Indicates whether or not this base DN registry is in test mode. 058 * A registry instance that is in test mode will not modify backend 059 * objects referred to in the above maps. 060 */ 061 private boolean testOnly; 062 063 /** 064 * Registers a base DN with this registry. 065 * 066 * @param baseDN to register 067 * @param backend with which the base DN is associated 068 * @param isPrivate indicates whether or not this base DN is private 069 * @return list of error messages generated by registering the base DN 070 * that should be logged if the changes to this registry are 071 * committed to the server 072 * @throws DirectoryException if the base DN cannot be registered 073 */ 074 public List<LocalizableMessage> registerBaseDN(DN baseDN, Backend<?> backend, boolean isPrivate) 075 throws DirectoryException 076 { 077 // Check to see if the base DN is already registered with the server. 078 Backend<?> existingBackend = baseDNs.get(baseDN); 079 if (existingBackend != null) 080 { 081 LocalizableMessage message = ERR_REGISTER_BASEDN_ALREADY_EXISTS. 082 get(baseDN, backend.getBackendID(), existingBackend.getBackendID()); 083 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 084 } 085 086 087 // Check to see if the backend is already registered with the server for 088 // any other base DN(s). The new base DN must not have any hierarchical 089 // relationship with any other base Dns for the same backend. 090 LinkedList<DN> otherBaseDNs = new LinkedList<>(); 091 for (DN dn : baseDNs.keySet()) 092 { 093 Backend<?> b = baseDNs.get(dn); 094 if (b.equals(backend)) 095 { 096 otherBaseDNs.add(dn); 097 098 if (baseDN.isAncestorOf(dn) || baseDN.isDescendantOf(dn)) 099 { 100 LocalizableMessage message = ERR_REGISTER_BASEDN_HIERARCHY_CONFLICT. 101 get(baseDN, backend.getBackendID(), dn); 102 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 103 } 104 } 105 } 106 107 108 // Check to see if the new base DN is subordinate to any other base DN 109 // already defined. If it is, then any other base DN(s) for the same 110 // backend must also be subordinate to the same base DN. 111 final Backend<?> superiorBackend = getSuperiorBackend(baseDN, otherBaseDNs, backend.getBackendID()); 112 if (superiorBackend == null && backend.getParentBackend() != null) 113 { 114 LocalizableMessage message = ERR_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE. 115 get(baseDN, backend.getBackendID(), backend.getParentBackend().getBackendID()); 116 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 117 } 118 119 120 // Check to see if the new base DN should be the superior base DN for any 121 // other base DN(s) already defined. 122 LinkedList<Backend<?>> subordinateBackends = new LinkedList<>(); 123 LinkedList<DN> subordinateBaseDNs = new LinkedList<>(); 124 for (DN dn : baseDNs.keySet()) 125 { 126 Backend<?> b = baseDNs.get(dn); 127 DN parentDN = dn.parent(); 128 while (parentDN != null) 129 { 130 if (parentDN.equals(baseDN)) 131 { 132 subordinateBaseDNs.add(dn); 133 subordinateBackends.add(b); 134 break; 135 } 136 else if (baseDNs.containsKey(parentDN)) 137 { 138 break; 139 } 140 141 parentDN = parentDN.parent(); 142 } 143 } 144 145 146 // If we've gotten here, then the new base DN is acceptable. If we should 147 // actually apply the changes then do so now. 148 final List<LocalizableMessage> errors = new LinkedList<>(); 149 150 // Check to see if any of the registered backends already contain an 151 // entry with the DN specified as the base DN. This could happen if 152 // we're creating a new subordinate backend in an existing directory 153 // (e.g., moving the "ou=People,dc=example,dc=com" branch to its own 154 // backend when that data already exists under the "dc=example,dc=com" 155 // backend). This condition shouldn't prevent the new base DN from 156 // being registered, but it's definitely important enough that we let 157 // the administrator know about it and remind them that the existing 158 // backend will need to be reinitialized. 159 if (superiorBackend != null && superiorBackend.entryExists(baseDN)) 160 { 161 errors.add(WARN_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS. 162 get(superiorBackend.getBackendID(), baseDN, backend.getBackendID())); 163 } 164 165 166 baseDNs.put(baseDN, backend); 167 168 if (superiorBackend == null) 169 { 170 if (!testOnly) 171 { 172 backend.setPrivateBackend(isPrivate); 173 } 174 175 if (isPrivate) 176 { 177 privateNamingContexts.put(baseDN, backend); 178 } 179 else 180 { 181 publicNamingContexts.put(baseDN, backend); 182 } 183 } 184 else if (otherBaseDNs.isEmpty() && !testOnly) 185 { 186 backend.setParentBackend(superiorBackend); 187 superiorBackend.addSubordinateBackend(backend); 188 } 189 190 if (!testOnly) 191 { 192 for (Backend<?> b : subordinateBackends) 193 { 194 Backend<?> oldParentBackend = b.getParentBackend(); 195 if (oldParentBackend != null) 196 { 197 oldParentBackend.removeSubordinateBackend(b); 198 } 199 200 b.setParentBackend(backend); 201 backend.addSubordinateBackend(b); 202 } 203 } 204 205 for (DN dn : subordinateBaseDNs) 206 { 207 publicNamingContexts.remove(dn); 208 privateNamingContexts.remove(dn); 209 } 210 211 return errors; 212 } 213 214 private Backend<?> getSuperiorBackend(DN baseDN, LinkedList<DN> otherBaseDNs, String backendID) 215 throws DirectoryException 216 { 217 Backend<?> superiorBackend = null; 218 DN parentDN = baseDN.parent(); 219 while (parentDN != null) 220 { 221 if (baseDNs.containsKey(parentDN)) 222 { 223 superiorBackend = baseDNs.get(parentDN); 224 225 for (DN dn : otherBaseDNs) 226 { 227 if (!dn.isDescendantOf(parentDN)) 228 { 229 LocalizableMessage msg = ERR_REGISTER_BASEDN_DIFFERENT_PARENT_BASES.get(baseDN, backendID, dn); 230 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg); 231 } 232 } 233 break; 234 } 235 236 parentDN = parentDN.parent(); 237 } 238 return superiorBackend; 239 } 240 241 242 /** 243 * Deregisters a base DN with this registry. 244 * 245 * @param baseDN to deregister 246 * @return list of error messages generated by deregistering the base DN 247 * that should be logged if the changes to this registry are 248 * committed to the server 249 * @throws DirectoryException if the base DN could not be deregistered 250 */ 251 public List<LocalizableMessage> deregisterBaseDN(DN baseDN) 252 throws DirectoryException 253 { 254 ifNull(baseDN); 255 256 // Make sure that the Directory Server actually contains a backend with 257 // the specified base DN. 258 Backend<?> backend = baseDNs.get(baseDN); 259 if (backend == null) 260 { 261 LocalizableMessage message = 262 ERR_DEREGISTER_BASEDN_NOT_REGISTERED.get(baseDN); 263 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 264 } 265 266 267 // Check to see if the backend has a parent backend, and whether it has 268 // any subordinates with base DNs that are below the base DN to remove. 269 Backend<?> superiorBackend = backend.getParentBackend(); 270 LinkedList<Backend<?>> subordinateBackends = new LinkedList<>(); 271 if (backend.getSubordinateBackends() != null) 272 { 273 for (Backend<?> b : backend.getSubordinateBackends()) 274 { 275 for (DN dn : b.getBaseDNs()) 276 { 277 if (dn.isDescendantOf(baseDN)) 278 { 279 subordinateBackends.add(b); 280 break; 281 } 282 } 283 } 284 } 285 286 287 // See if there are any other base DNs registered within the same backend. 288 LinkedList<DN> otherBaseDNs = new LinkedList<>(); 289 for (DN dn : baseDNs.keySet()) 290 { 291 if (dn.equals(baseDN)) 292 { 293 continue; 294 } 295 296 Backend<?> b = baseDNs.get(dn); 297 if (backend.equals(b)) 298 { 299 otherBaseDNs.add(dn); 300 } 301 } 302 303 304 // If we've gotten here, then it's OK to make the changes. 305 306 // Get rid of the references to this base DN in the mapping tree 307 // information. 308 baseDNs.remove(baseDN); 309 publicNamingContexts.remove(baseDN); 310 privateNamingContexts.remove(baseDN); 311 312 final LinkedList<LocalizableMessage> errors = new LinkedList<>(); 313 if (superiorBackend == null) 314 { 315 // If there were any subordinate backends, then all of their base DNs 316 // will now be promoted to naming contexts. 317 for (Backend<?> b : subordinateBackends) 318 { 319 if (!testOnly) 320 { 321 b.setParentBackend(null); 322 backend.removeSubordinateBackend(b); 323 } 324 325 for (DN dn : b.getBaseDNs()) 326 { 327 if (b.isPrivateBackend()) 328 { 329 privateNamingContexts.put(dn, b); 330 } 331 else 332 { 333 publicNamingContexts.put(dn, b); 334 } 335 } 336 } 337 } 338 else 339 { 340 // If there are no other base DNs for the associated backend, then 341 // remove this backend as a subordinate of the parent backend. 342 if (otherBaseDNs.isEmpty() && !testOnly) 343 { 344 superiorBackend.removeSubordinateBackend(backend); 345 } 346 347 348 // If there are any subordinate backends, then they need to be made 349 // subordinate to the parent backend. Also, we should log a warning 350 // message indicating that there may be inconsistent search results 351 // because some of the structural entries will be missing. 352 if (! subordinateBackends.isEmpty()) 353 { 354 // Suppress this warning message on server shutdown. 355 if (!DirectoryServer.getInstance().isShuttingDown()) { 356 errors.add(WARN_DEREGISTER_BASEDN_MISSING_HIERARCHY.get( 357 baseDN, backend.getBackendID())); 358 } 359 360 if (!testOnly) 361 { 362 for (Backend<?> b : subordinateBackends) 363 { 364 backend.removeSubordinateBackend(b); 365 superiorBackend.addSubordinateBackend(b); 366 b.setParentBackend(superiorBackend); 367 } 368 } 369 } 370 } 371 return errors; 372 } 373 374 375 /** 376 * Creates a default instance. 377 */ 378 BaseDnRegistry() 379 { 380 this(false); 381 } 382 383 /** 384 * Returns a copy of this registry. 385 * 386 * @return copy of this registry 387 */ 388 BaseDnRegistry copy() 389 { 390 final BaseDnRegistry registry = new BaseDnRegistry(true); 391 registry.baseDNs.putAll(baseDNs); 392 registry.publicNamingContexts.putAll(publicNamingContexts); 393 registry.privateNamingContexts.putAll(privateNamingContexts); 394 return registry; 395 } 396 397 398 /** 399 * Creates a parameterized instance. 400 * 401 * @param testOnly indicates whether this registry will be used for testing; 402 * when <code>true</code> this registry will not modify backends 403 */ 404 private BaseDnRegistry(boolean testOnly) 405 { 406 this.testOnly = testOnly; 407 } 408 409 410 /** 411 * Gets the mapping of registered base DNs to their associated backend. 412 * 413 * @return mapping from base DN to backend 414 */ 415 Map<DN,Backend> getBaseDnMap() { 416 return this.baseDNs; 417 } 418 419 420 /** 421 * Gets the mapping of registered public naming contexts to their 422 * associated backend. 423 * 424 * @return mapping from naming context to backend 425 */ 426 Map<DN,Backend> getPublicNamingContextsMap() { 427 return this.publicNamingContexts; 428 } 429 430 431 /** 432 * Gets the mapping of registered private naming contexts to their 433 * associated backend. 434 * 435 * @return mapping from naming context to backend 436 */ 437 Map<DN,Backend> getPrivateNamingContextsMap() { 438 return this.privateNamingContexts; 439 } 440 441 442 443 444 /** 445 * Indicates whether the specified DN is contained in this registry as 446 * a naming contexts. 447 * 448 * @param dn The DN for which to make the determination. 449 * 450 * @return {@code true} if the specified DN is a naming context in this 451 * registry, or {@code false} if it is not. 452 */ 453 boolean containsNamingContext(DN dn) 454 { 455 return privateNamingContexts.containsKey(dn) || publicNamingContexts.containsKey(dn); 456 } 457 458 459 /** 460 * Clear and nullify this registry's internal state. 461 */ 462 void clear() { 463 baseDNs.clear(); 464 privateNamingContexts.clear(); 465 publicNamingContexts.clear(); 466 } 467 468}