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}