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}