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