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