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 java.util.LinkedList; 020import java.util.List; 021import java.util.Map; 022import java.util.TreeMap; 023 024import org.forgerock.i18n.LocalizableMessage; 025import org.forgerock.opendj.ldap.ResultCode; 026import org.opends.server.api.Backend; 027import org.forgerock.opendj.ldap.DN; 028import org.opends.server.types.DirectoryException; 029 030import static org.forgerock.util.Reject.*; 031import static org.opends.messages.CoreMessages.*; 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 039 /** The set of base DNs registered with the server. */ 040 private final TreeMap<DN, Backend> baseDNs = new TreeMap<>(); 041 /** The set of private naming contexts registered with the server. */ 042 private final TreeMap<DN, Backend> privateNamingContexts = new TreeMap<>(); 043 /** The set of public naming contexts registered with the server. */ 044 private final TreeMap<DN, Backend> publicNamingContexts = new TreeMap<>(); 045 046 /** 047 * Indicates whether or not this base DN registry is in test mode. 048 * A registry instance that is in test mode will not modify backend 049 * objects referred to in the above maps. 050 */ 051 private boolean testOnly; 052 053 /** 054 * Registers a base DN with this registry. 055 * 056 * @param baseDN to register 057 * @param backend with which the base DN is associated 058 * @param isPrivate indicates whether or not this base DN is private 059 * @return list of error messages generated by registering the base DN 060 * that should be logged if the changes to this registry are 061 * committed to the server 062 * @throws DirectoryException if the base DN cannot be registered 063 */ 064 public List<LocalizableMessage> registerBaseDN(DN baseDN, Backend<?> backend, boolean isPrivate) 065 throws DirectoryException 066 { 067 // Check to see if the base DN is already registered with the server. 068 Backend<?> existingBackend = baseDNs.get(baseDN); 069 if (existingBackend != null) 070 { 071 LocalizableMessage message = ERR_REGISTER_BASEDN_ALREADY_EXISTS. 072 get(baseDN, backend.getBackendID(), existingBackend.getBackendID()); 073 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 074 } 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 098 // Check to see if the new base DN is subordinate to any other base DN 099 // already defined. If it is, then any other base DN(s) for the same 100 // backend must also be subordinate to the same base DN. 101 final Backend<?> superiorBackend = getSuperiorBackend(baseDN, otherBaseDNs, backend.getBackendID()); 102 if (superiorBackend == null && backend.getParentBackend() != null) 103 { 104 LocalizableMessage message = ERR_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE. 105 get(baseDN, backend.getBackendID(), backend.getParentBackend().getBackendID()); 106 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 107 } 108 109 110 // Check to see if the new base DN should be the superior base DN for any 111 // other base DN(s) already defined. 112 LinkedList<Backend<?>> subordinateBackends = new LinkedList<>(); 113 LinkedList<DN> subordinateBaseDNs = new LinkedList<>(); 114 for (DN dn : baseDNs.keySet()) 115 { 116 Backend<?> b = baseDNs.get(dn); 117 DN parentDN = dn.parent(); 118 while (parentDN != null) 119 { 120 if (parentDN.equals(baseDN)) 121 { 122 subordinateBaseDNs.add(dn); 123 subordinateBackends.add(b); 124 break; 125 } 126 else if (baseDNs.containsKey(parentDN)) 127 { 128 break; 129 } 130 131 parentDN = parentDN.parent(); 132 } 133 } 134 135 136 // If we've gotten here, then the new base DN is acceptable. If we should 137 // actually apply the changes then do so now. 138 final List<LocalizableMessage> errors = new LinkedList<>(); 139 140 // Check to see if any of the registered backends already contain an 141 // entry with the DN specified as the base DN. This could happen if 142 // we're creating a new subordinate backend in an existing directory 143 // (e.g., moving the "ou=People,dc=example,dc=com" branch to its own 144 // backend when that data already exists under the "dc=example,dc=com" 145 // backend). This condition shouldn't prevent the new base DN from 146 // being registered, but it's definitely important enough that we let 147 // the administrator know about it and remind them that the existing 148 // backend will need to be reinitialized. 149 if (superiorBackend != null && superiorBackend.entryExists(baseDN)) 150 { 151 errors.add(WARN_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS. 152 get(superiorBackend.getBackendID(), baseDN, backend.getBackendID())); 153 } 154 155 156 baseDNs.put(baseDN, backend); 157 158 if (superiorBackend == null) 159 { 160 if (!testOnly) 161 { 162 backend.setPrivateBackend(isPrivate); 163 } 164 165 if (isPrivate) 166 { 167 privateNamingContexts.put(baseDN, backend); 168 } 169 else 170 { 171 publicNamingContexts.put(baseDN, backend); 172 } 173 } 174 else if (otherBaseDNs.isEmpty() && !testOnly) 175 { 176 backend.setParentBackend(superiorBackend); 177 superiorBackend.addSubordinateBackend(backend); 178 } 179 180 if (!testOnly) 181 { 182 for (Backend<?> b : subordinateBackends) 183 { 184 Backend<?> oldParentBackend = b.getParentBackend(); 185 if (oldParentBackend != null) 186 { 187 oldParentBackend.removeSubordinateBackend(b); 188 } 189 190 b.setParentBackend(backend); 191 backend.addSubordinateBackend(b); 192 } 193 } 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 /** 233 * Deregisters a base DN with this registry. 234 * 235 * @param baseDN to deregister 236 * @return list of error messages generated by deregistering the base DN 237 * that should be logged if the changes to this registry are 238 * committed to the server 239 * @throws DirectoryException if the base DN could not be deregistered 240 */ 241 public List<LocalizableMessage> deregisterBaseDN(DN baseDN) 242 throws DirectoryException 243 { 244 ifNull(baseDN); 245 246 // Make sure that the Directory Server actually contains a backend with 247 // the specified base DN. 248 Backend<?> backend = baseDNs.get(baseDN); 249 if (backend == null) 250 { 251 LocalizableMessage message = 252 ERR_DEREGISTER_BASEDN_NOT_REGISTERED.get(baseDN); 253 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 254 } 255 256 257 // Check to see if the backend has a parent backend, and whether it has 258 // any subordinates with base DNs that are below the base DN to remove. 259 Backend<?> superiorBackend = backend.getParentBackend(); 260 LinkedList<Backend<?>> subordinateBackends = new LinkedList<>(); 261 if (backend.getSubordinateBackends() != null) 262 { 263 for (Backend<?> b : backend.getSubordinateBackends()) 264 { 265 for (DN dn : b.getBaseDNs()) 266 { 267 if (dn.isSubordinateOrEqualTo(baseDN)) 268 { 269 subordinateBackends.add(b); 270 break; 271 } 272 } 273 } 274 } 275 276 277 // See if there are any other base DNs registered within the same backend. 278 LinkedList<DN> otherBaseDNs = new LinkedList<>(); 279 for (DN dn : baseDNs.keySet()) 280 { 281 if (dn.equals(baseDN)) 282 { 283 continue; 284 } 285 286 Backend<?> b = baseDNs.get(dn); 287 if (backend.equals(b)) 288 { 289 otherBaseDNs.add(dn); 290 } 291 } 292 293 294 // If we've gotten here, then it's OK to make the changes. 295 296 // Get rid of the references to this base DN in the mapping tree 297 // information. 298 baseDNs.remove(baseDN); 299 publicNamingContexts.remove(baseDN); 300 privateNamingContexts.remove(baseDN); 301 302 final LinkedList<LocalizableMessage> errors = new LinkedList<>(); 303 if (superiorBackend == null) 304 { 305 // If there were any subordinate backends, then all of their base DNs 306 // will now be promoted to naming contexts. 307 for (Backend<?> b : subordinateBackends) 308 { 309 if (!testOnly) 310 { 311 b.setParentBackend(null); 312 backend.removeSubordinateBackend(b); 313 } 314 315 for (DN dn : b.getBaseDNs()) 316 { 317 if (b.isPrivateBackend()) 318 { 319 privateNamingContexts.put(dn, b); 320 } 321 else 322 { 323 publicNamingContexts.put(dn, b); 324 } 325 } 326 } 327 } 328 else 329 { 330 // If there are no other base DNs for the associated backend, then 331 // remove this backend as a subordinate of the parent backend. 332 if (otherBaseDNs.isEmpty() && !testOnly) 333 { 334 superiorBackend.removeSubordinateBackend(backend); 335 } 336 337 338 // If there are any subordinate backends, then they need to be made 339 // subordinate to the parent backend. Also, we should log a warning 340 // message indicating that there may be inconsistent search results 341 // because some of the structural entries will be missing. 342 if (! subordinateBackends.isEmpty()) 343 { 344 // Suppress this warning message on server shutdown. 345 if (!DirectoryServer.getInstance().isShuttingDown()) { 346 errors.add(WARN_DEREGISTER_BASEDN_MISSING_HIERARCHY.get( 347 baseDN, backend.getBackendID())); 348 } 349 350 if (!testOnly) 351 { 352 for (Backend<?> b : subordinateBackends) 353 { 354 backend.removeSubordinateBackend(b); 355 superiorBackend.addSubordinateBackend(b); 356 b.setParentBackend(superiorBackend); 357 } 358 } 359 } 360 } 361 return errors; 362 } 363 364 365 /** 366 * Creates a default instance. 367 */ 368 BaseDnRegistry() 369 { 370 this(false); 371 } 372 373 /** 374 * Returns a copy of this registry. 375 * 376 * @return copy of this registry 377 */ 378 BaseDnRegistry copy() 379 { 380 final BaseDnRegistry registry = new BaseDnRegistry(true); 381 registry.baseDNs.putAll(baseDNs); 382 registry.publicNamingContexts.putAll(publicNamingContexts); 383 registry.privateNamingContexts.putAll(privateNamingContexts); 384 return registry; 385 } 386 387 388 /** 389 * Creates a parameterized instance. 390 * 391 * @param testOnly indicates whether this registry will be used for testing; 392 * when <code>true</code> this registry will not modify backends 393 */ 394 private BaseDnRegistry(boolean testOnly) 395 { 396 this.testOnly = testOnly; 397 } 398 399 400 /** 401 * Gets the mapping of registered base DNs to their associated backend. 402 * 403 * @return mapping from base DN to backend 404 */ 405 Map<DN,Backend> getBaseDnMap() { 406 return this.baseDNs; 407 } 408 409 410 /** 411 * Gets the mapping of registered public naming contexts to their 412 * associated backend. 413 * 414 * @return mapping from naming context to backend 415 */ 416 Map<DN,Backend> getPublicNamingContextsMap() { 417 return this.publicNamingContexts; 418 } 419 420 421 /** 422 * Gets the mapping of registered private naming contexts to their 423 * associated backend. 424 * 425 * @return mapping from naming context to backend 426 */ 427 Map<DN,Backend> getPrivateNamingContextsMap() { 428 return this.privateNamingContexts; 429 } 430 431 432 433 434 /** 435 * Indicates whether the specified DN is contained in this registry as 436 * a naming contexts. 437 * 438 * @param dn The DN for which to make the determination. 439 * 440 * @return {@code true} if the specified DN is a naming context in this 441 * registry, or {@code false} if it is not. 442 */ 443 boolean containsNamingContext(DN dn) 444 { 445 return privateNamingContexts.containsKey(dn) || publicNamingContexts.containsKey(dn); 446 } 447 448 449 /** 450 * Clear and nullify this registry's internal state. 451 */ 452 void clear() { 453 baseDNs.clear(); 454 privateNamingContexts.clear(); 455 publicNamingContexts.clear(); 456 } 457 458}