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}