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 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.backends;
028    
029    
030    
031    import java.util.ArrayList;
032    import java.util.Collection;
033    import java.util.HashMap;
034    import java.util.HashSet;
035    import java.util.LinkedHashSet;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Set;
039    import java.util.TreeSet;
040    import java.util.concurrent.ConcurrentHashMap;
041    
042    import org.opends.messages.Message;
043    import org.opends.server.admin.Configuration;
044    import org.opends.server.admin.server.ConfigurationChangeListener;
045    import org.opends.server.admin.std.server.RootDSEBackendCfg;
046    import org.opends.server.api.Backend;
047    import org.opends.server.config.ConfigEntry;
048    import org.opends.server.config.ConfigException;
049    import org.opends.server.core.AddOperation;
050    import org.opends.server.core.DeleteOperation;
051    import org.opends.server.core.DirectoryServer;
052    import org.opends.server.core.ModifyOperation;
053    import org.opends.server.core.ModifyDNOperation;
054    import org.opends.server.core.SearchOperation;
055    import org.opends.server.loggers.debug.DebugTracer;
056    import org.opends.server.protocols.asn1.ASN1OctetString;
057    import org.opends.server.types.*;
058    import org.opends.server.util.LDIFWriter;
059    import org.opends.server.util.Validator;
060    
061    import static org.opends.messages.BackendMessages.*;
062    import static org.opends.messages.ConfigMessages.
063         ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY;
064    import static org.opends.server.config.ConfigConstants.*;
065    import static org.opends.server.loggers.debug.DebugLogger.*;
066    import static org.opends.server.loggers.ErrorLogger.*;
067    import static org.opends.server.util.ServerConstants.*;
068    import static org.opends.server.util.StaticUtils.*;
069    
070    
071    
072    /**
073     * This class defines a backend to hold the Directory Server root DSE.  It is a
074     * kind of meta-backend in that it will dynamically generate the root DSE entry
075     * (although there will be some caching) for base-level searches, and will
076     * simply redirect to other backends for operations in other scopes.
077     * <BR><BR>
078     * This should not be treated like a regular backend when it comes to
079     * initializing the server configuration.  It should only be initialized after
080     * all other backends are configured.  As such, it should have a special entry
081     * in the configuration rather than being placed under the cn=Backends branch
082     * with the other backends.
083     */
084    public class RootDSEBackend
085           extends Backend
086           implements ConfigurationChangeListener<RootDSEBackendCfg>
087    {
088      /**
089       * The tracer object for the debug logger.
090       */
091      private static final DebugTracer TRACER = getTracer();
092    
093    
094    
095      // The set of standard "static" attributes that we will always include in the
096      // root DSE entry and won't change while the server is running.
097      private ArrayList<Attribute> staticDSEAttributes;
098    
099      // The set of user-defined attributes that will be included in the root DSE
100      // entry.
101      private ArrayList<Attribute> userDefinedAttributes;
102    
103      // Indicates whether the attributes of the root DSE should always be treated
104      // as user attributes even if they are defined as operational in the schema.
105      private boolean showAllAttributes;
106    
107      // The set of subordinate base DNs and their associated backends that will be
108      // used for non-base searches.
109      private ConcurrentHashMap<DN,Backend> subordinateBaseDNs;
110    
111      // The set of objectclasses that will be used in the root DSE entry.
112      private HashMap<ObjectClass,String> dseObjectClasses;
113    
114      // The current configuration state.
115      private RootDSEBackendCfg currentConfig;
116    
117      // The DN of the configuration entry for this backend.
118      private DN configEntryDN;
119    
120      // The DN for the root DSE.
121      private DN rootDSEDN;
122    
123      // The set of base DNs for this backend.
124      private DN[] baseDNs;
125    
126      // The set of supported controls for this backend.
127      private HashSet<String> supportedControls;
128    
129      // The set of supported features for this backend.
130      private HashSet<String> supportedFeatures;
131    
132    
133    
134      /**
135       * Creates a new backend with the provided information.  All backend
136       * implementations must implement a default constructor that use
137       * <CODE>super()</CODE> to invoke this constructor.
138       */
139      public RootDSEBackend()
140      {
141        super();
142    
143        // Perform all initialization in initializeBackend.
144      }
145    
146    
147    
148      /**
149       * {@inheritDoc}
150       */
151      @Override()
152      public void configureBackend(Configuration config)
153             throws ConfigException
154      {
155        Validator.ensureNotNull(config);
156        Validator.ensureTrue(config instanceof RootDSEBackendCfg);
157        currentConfig = (RootDSEBackendCfg)config;
158        configEntryDN = config.dn();
159      }
160    
161    
162    
163      /**
164       * {@inheritDoc}
165       */
166      @Override()
167      public void initializeBackend()
168             throws ConfigException, InitializationException
169      {
170        ConfigEntry configEntry =
171             DirectoryServer.getConfigEntry(configEntryDN);
172    
173        // Make sure that a configuration entry was provided.  If not, then we will
174        // not be able to complete initialization.
175        if (configEntry == null)
176        {
177          Message message = ERR_ROOTDSE_CONFIG_ENTRY_NULL.get();
178          throw new ConfigException(message);
179        }
180    
181        // Get the set of user-defined attributes for the configuration entry.  Any
182        // attributes that we don't recognize will be included directly in the root
183        // DSE.
184        userDefinedAttributes = new ArrayList<Attribute>();
185        for (List<Attribute> attrs :
186             configEntry.getEntry().getUserAttributes().values())
187        {
188          for (Attribute a : attrs)
189          {
190            if (! isDSEConfigAttribute(a))
191            {
192              userDefinedAttributes.add(a);
193            }
194          }
195        }
196        for (List<Attribute> attrs :
197             configEntry.getEntry().getOperationalAttributes().values())
198        {
199          for (Attribute a : attrs)
200          {
201            if (! isDSEConfigAttribute(a))
202            {
203              userDefinedAttributes.add(a);
204            }
205          }
206        }
207    
208    
209        // Create the set of base DNs that we will handle.  In this case, it's just
210        // the root DSE.
211        rootDSEDN    = DN.nullDN();
212        this.baseDNs = new DN[] { rootDSEDN };
213    
214    
215        // Create the set of subordinate base DNs.  If this is specified in the
216        // configuration, then use that set.  Otherwise, use the set of non-private
217        // backends defined in the server.
218        try
219        {
220          Set<DN> subDNs = currentConfig.getSubordinateBaseDN();
221          if (subDNs.isEmpty())
222          {
223            // This is fine -- we'll just use the set of user-defined suffixes.
224            subordinateBaseDNs = null;
225          }
226          else
227          {
228            subordinateBaseDNs = new ConcurrentHashMap<DN,Backend>();
229            for (DN baseDN : subDNs)
230            {
231              Backend backend = DirectoryServer.getBackend(baseDN);
232              if (backend == null)
233              {
234                Message message = WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(
235                    String.valueOf(baseDN));
236                logError(message);
237              }
238              else
239              {
240                subordinateBaseDNs.put(baseDN, backend);
241              }
242            }
243          }
244        }
245        catch (Exception e)
246        {
247          if (debugEnabled())
248          {
249            TRACER.debugCaught(DebugLogLevel.ERROR, e);
250          }
251    
252          Message message = WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get(
253              stackTraceToSingleLineString(e));
254          throw new InitializationException(message, e);
255        }
256    
257    
258        // Determine whether all root DSE attributes should be treated as user
259        // attributes.
260        showAllAttributes = currentConfig.isShowAllAttributes();
261    
262    
263        // Construct the set of "static" attributes that will always be present in
264        // the root DSE.
265        staticDSEAttributes = new ArrayList<Attribute>();
266    
267        staticDSEAttributes.add(createAttribute(ATTR_VENDOR_NAME,
268                                                ATTR_VENDOR_NAME_LC,
269                                                SERVER_VENDOR_NAME));
270    
271        staticDSEAttributes.add(createAttribute(ATTR_VENDOR_VERSION,
272                                     ATTR_VENDOR_VERSION_LC,
273                                     DirectoryServer.getVersionString()));
274    
275    
276    
277        // Construct the set of objectclasses to include in the root DSE entry.
278        dseObjectClasses = new HashMap<ObjectClass,String>(2);
279        ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP);
280        if (topOC == null)
281        {
282          topOC = DirectoryServer.getDefaultObjectClass(OC_TOP);
283        }
284        dseObjectClasses.put(topOC, OC_TOP);
285    
286        ObjectClass rootDSEOC =
287             DirectoryServer.getObjectClass(OC_ROOT_DSE);
288        if (rootDSEOC == null)
289        {
290          rootDSEOC = DirectoryServer.getDefaultObjectClass(OC_ROOT_DSE);
291        }
292        dseObjectClasses.put(rootDSEOC, OC_ROOT_DSE);
293    
294    
295        // Define an empty sets for the supported controls and features.
296        supportedControls = new HashSet<String>(0);
297        supportedFeatures = new HashSet<String>(0);
298    
299    
300        // Set the backend ID for this backend. The identifier needs to be
301        // specific enough to avoid conflict with user backend identifiers.
302        setBackendID("__root.dse__");
303    
304    
305        // Register as a change listener.
306        currentConfig.addChangeListener(this);
307      }
308    
309    
310    
311      /**
312       * {@inheritDoc}
313       */
314      @Override()
315      public void finalizeBackend()
316      {
317        currentConfig.removeChangeListener(this);
318      }
319    
320    
321    
322      /**
323       * Indicates whether the provided attribute is one that is used in the
324       * configuration of this backend.
325       *
326       * @param  attribute  The attribute for which to make the determination.
327       *
328       * @return  <CODE>true</CODE> if the provided attribute is one that is used in
329       *          the configuration of this backend, <CODE>false</CODE> if not.
330       */
331      private boolean isDSEConfigAttribute(Attribute attribute)
332      {
333        AttributeType attrType = attribute.getAttributeType();
334        if (attrType.hasName(ATTR_ROOT_DSE_SUBORDINATE_BASE_DN.toLowerCase()) ||
335            attrType.hasName(ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES.toLowerCase()) ||
336            attrType.hasName(ATTR_COMMON_NAME))
337        {
338          return true;
339        }
340    
341        return false;
342      }
343    
344    
345    
346      /**
347       * {@inheritDoc}
348       */
349      @Override()
350      public DN[] getBaseDNs()
351      {
352        return baseDNs;
353      }
354    
355    
356    
357      /**
358       * {@inheritDoc}
359       */
360      @Override()
361      public synchronized long getEntryCount()
362      {
363        // There is always just a single entry in this backend.
364        return 1;
365      }
366    
367    
368    
369      /**
370       * {@inheritDoc}
371       */
372      @Override()
373      public boolean isLocal()
374      {
375        // For the purposes of this method, this is a local backend.
376        return true;
377      }
378    
379    
380    
381      /**
382       * {@inheritDoc}
383       */
384      @Override()
385      public boolean isIndexed(AttributeType attributeType, IndexType indexType)
386      {
387        // All searches in this backend will always be considered indexed.
388        return true;
389      }
390    
391    
392    
393      /**
394       * {@inheritDoc}
395       */
396      @Override()
397      public ConditionResult hasSubordinates(DN entryDN)
398             throws DirectoryException
399      {
400        long ret = numSubordinates(entryDN, false);
401        if(ret < 0)
402        {
403          return ConditionResult.UNDEFINED;
404        }
405        else if(ret == 0)
406        {
407          return ConditionResult.FALSE;
408        }
409        else
410        {
411          return ConditionResult.TRUE;
412        }
413      }
414    
415    
416    
417      /**
418       * {@inheritDoc}
419       */
420      @Override()
421      public long numSubordinates(DN entryDN, boolean subtree)
422             throws DirectoryException
423      {
424        if (entryDN == null || ! entryDN.isNullDN())
425        {
426          return -1;
427        }
428    
429        long count = 0;
430    
431        Map<DN,Backend> baseMap;
432        if (subordinateBaseDNs == null)
433        {
434          baseMap = DirectoryServer.getPublicNamingContexts();
435        }
436        else
437        {
438          baseMap = subordinateBaseDNs;
439        }
440    
441        for (DN subBase : baseMap.keySet())
442        {
443          Backend b = baseMap.get(subBase);
444          Entry subBaseEntry = b.getEntry(subBase);
445          if (subBaseEntry != null)
446          {
447            if(subtree)
448            {
449              long subCount = b.numSubordinates(subBase, true);
450              if(subCount < 0)
451              {
452                return -1;
453              }
454    
455              count += subCount;
456            }
457            count ++;
458          }
459        }
460    
461        return count;
462      }
463    
464    
465    
466      /**
467       * {@inheritDoc}
468       */
469      @Override()
470      public Entry getEntry(DN entryDN)
471             throws DirectoryException
472      {
473        // If the requested entry was the root DSE, then create and return it.
474        if ((entryDN == null) || entryDN.isNullDN())
475        {
476          return getRootDSE();
477        }
478    
479    
480        // This method should never be used to get anything other than the root DSE.
481        // If we got here, then that appears to be the case, so log a message.
482        Message message =
483            WARN_ROOTDSE_GET_ENTRY_NONROOT.get(String.valueOf(entryDN));
484        logError(message);
485    
486    
487        // Go ahead and check the subordinate backends to see if we can find the
488        // entry there.  Note that in order to avoid potential loop conditions, this
489        // will only work if the set of subordinate bases has been explicitly
490        // specified.
491        if (subordinateBaseDNs != null)
492        {
493          for (Backend b : subordinateBaseDNs.values())
494          {
495            if (b.handlesEntry(entryDN))
496            {
497              return b.getEntry(entryDN);
498            }
499          }
500        }
501    
502    
503        // If we've gotten here, then we couldn't find the entry so return null.
504        return null;
505      }
506    
507    
508    
509      /**
510       * Retrieves the root DSE entry for the Directory Server.
511       *
512       * @return  The root DSE entry for the Directory Server.
513       */
514      public Entry getRootDSE()
515      {
516        HashMap<AttributeType,List<Attribute>> dseUserAttrs =
517             new HashMap<AttributeType,List<Attribute>>();
518    
519        HashMap<AttributeType,List<Attribute>> dseOperationalAttrs =
520             new HashMap<AttributeType,List<Attribute>>();
521    
522    
523        // Add the "namingContexts" attribute.
524        Attribute publicNamingContextAttr =
525             createDNAttribute(ATTR_NAMING_CONTEXTS, ATTR_NAMING_CONTEXTS_LC,
526                               DirectoryServer.getPublicNamingContexts().keySet());
527        ArrayList<Attribute> publicNamingContextAttrs = new ArrayList<Attribute>(1);
528        publicNamingContextAttrs.add(publicNamingContextAttr);
529        if (showAllAttributes ||
530            (! publicNamingContextAttr.getAttributeType().isOperational()))
531        {
532          dseUserAttrs.put(publicNamingContextAttr.getAttributeType(),
533                           publicNamingContextAttrs);
534        }
535        else
536        {
537          dseOperationalAttrs.put(publicNamingContextAttr.getAttributeType(),
538                                  publicNamingContextAttrs);
539        }
540    
541    
542        // Add the "ds-private-naming-contexts" attribute.
543        Attribute privateNamingContextAttr =
544             createDNAttribute(ATTR_PRIVATE_NAMING_CONTEXTS,
545                               ATTR_PRIVATE_NAMING_CONTEXTS,
546                               DirectoryServer.getPrivateNamingContexts().keySet());
547        ArrayList<Attribute> privateNamingContextAttrs =
548             new ArrayList<Attribute>(1);
549        privateNamingContextAttrs.add(privateNamingContextAttr);
550        if (showAllAttributes ||
551            (! privateNamingContextAttr.getAttributeType().isOperational()))
552        {
553          dseUserAttrs.put(privateNamingContextAttr.getAttributeType(),
554                           privateNamingContextAttrs);
555        }
556        else
557        {
558          dseOperationalAttrs.put(privateNamingContextAttr.getAttributeType(),
559                                  privateNamingContextAttrs);
560        }
561    
562    
563        // Add the "supportedControl" attribute.
564        Attribute supportedControlAttr =
565             createAttribute(ATTR_SUPPORTED_CONTROL, ATTR_SUPPORTED_CONTROL_LC,
566                             DirectoryServer.getSupportedControls());
567        ArrayList<Attribute> supportedControlAttrs = new ArrayList<Attribute>(1);
568        supportedControlAttrs.add(supportedControlAttr);
569        if (showAllAttributes ||
570            (! supportedControlAttr.getAttributeType().isOperational()))
571        {
572          dseUserAttrs.put(supportedControlAttr.getAttributeType(),
573                           supportedControlAttrs);
574        }
575        else
576        {
577          dseOperationalAttrs.put(supportedControlAttr.getAttributeType(),
578                                  supportedControlAttrs);
579        }
580    
581    
582        // Add the "supportedExtension" attribute.
583        Attribute supportedExtensionAttr =
584             createAttribute(ATTR_SUPPORTED_EXTENSION, ATTR_SUPPORTED_EXTENSION_LC,
585                             DirectoryServer.getSupportedExtensions().keySet());
586        ArrayList<Attribute> supportedExtensionAttrs = new ArrayList<Attribute>(1);
587        supportedExtensionAttrs.add(supportedExtensionAttr);
588        if (showAllAttributes ||
589            (! supportedExtensionAttr.getAttributeType().isOperational()))
590        {
591          dseUserAttrs.put(supportedExtensionAttr.getAttributeType(),
592                           supportedExtensionAttrs);
593        }
594        else
595        {
596          dseOperationalAttrs.put(supportedExtensionAttr.getAttributeType(),
597                                  supportedExtensionAttrs);
598        }
599    
600    
601        // Add the "supportedFeature" attribute.
602        Attribute supportedFeatureAttr =
603             createAttribute(ATTR_SUPPORTED_FEATURE, ATTR_SUPPORTED_FEATURE_LC,
604                             DirectoryServer.getSupportedFeatures());
605        ArrayList<Attribute> supportedFeatureAttrs = new ArrayList<Attribute>(1);
606        supportedFeatureAttrs.add(supportedFeatureAttr);
607        if (showAllAttributes ||
608            (! supportedFeatureAttr.getAttributeType().isOperational()))
609        {
610          dseUserAttrs.put(supportedFeatureAttr.getAttributeType(),
611                           supportedFeatureAttrs);
612        }
613        else
614        {
615          dseOperationalAttrs.put(supportedFeatureAttr.getAttributeType(),
616                                  supportedFeatureAttrs);
617        }
618    
619    
620        // Add the "supportedSASLMechanisms" attribute.
621        Attribute supportedSASLMechAttr =
622             createAttribute(ATTR_SUPPORTED_SASL_MECHANISMS,
623                             ATTR_SUPPORTED_SASL_MECHANISMS_LC,
624                             DirectoryServer.getSupportedSASLMechanisms().keySet());
625        ArrayList<Attribute> supportedSASLMechAttrs = new ArrayList<Attribute>(1);
626        supportedSASLMechAttrs.add(supportedSASLMechAttr);
627        if (showAllAttributes ||
628            (! supportedSASLMechAttr.getAttributeType().isOperational()))
629        {
630          dseUserAttrs.put(supportedSASLMechAttr.getAttributeType(),
631                           supportedSASLMechAttrs);
632        }
633        else
634        {
635          dseOperationalAttrs.put(supportedSASLMechAttr.getAttributeType(),
636                                  supportedSASLMechAttrs);
637        }
638    
639    
640        // Add the "supportedLDAPVersions" attribute.
641        TreeSet<String> versionStrings = new TreeSet<String>();
642        for (Integer ldapVersion : DirectoryServer.getSupportedLDAPVersions())
643        {
644          versionStrings.add(ldapVersion.toString());
645        }
646        Attribute supportedLDAPVersionAttr =
647             createAttribute(ATTR_SUPPORTED_LDAP_VERSION,
648                             ATTR_SUPPORTED_LDAP_VERSION_LC,
649                             versionStrings);
650        ArrayList<Attribute> supportedLDAPVersionAttrs =
651             new ArrayList<Attribute>(1);
652        supportedLDAPVersionAttrs.add(supportedLDAPVersionAttr);
653        if (showAllAttributes ||
654            (! supportedLDAPVersionAttr.getAttributeType().isOperational()))
655        {
656          dseUserAttrs.put(supportedLDAPVersionAttr.getAttributeType(),
657                           supportedLDAPVersionAttrs);
658        }
659        else
660        {
661          dseOperationalAttrs.put(supportedLDAPVersionAttr.getAttributeType(),
662                                  supportedLDAPVersionAttrs);
663        }
664    
665    
666        // Add the "supportedAuthPasswordSchemes" attribute.
667        Set<String> authPWSchemes =
668             DirectoryServer.getAuthPasswordStorageSchemes().keySet();
669        if (! authPWSchemes.isEmpty())
670        {
671          Attribute supportedAuthPWSchemesAttr =
672               createAttribute(ATTR_SUPPORTED_AUTH_PW_SCHEMES,
673                               ATTR_SUPPORTED_AUTH_PW_SCHEMES_LC, authPWSchemes);
674          ArrayList<Attribute> supportedAuthPWSchemesAttrs =
675               new ArrayList<Attribute>(1);
676          supportedAuthPWSchemesAttrs.add(supportedAuthPWSchemesAttr);
677          if (showAllAttributes ||
678              (! supportedSASLMechAttr.getAttributeType().isOperational()))
679          {
680            dseUserAttrs.put(supportedAuthPWSchemesAttr.getAttributeType(),
681                             supportedAuthPWSchemesAttrs);
682          }
683          else
684          {
685            dseOperationalAttrs.put(supportedAuthPWSchemesAttr.getAttributeType(),
686                                    supportedAuthPWSchemesAttrs);
687          }
688        }
689    
690    
691        // Add all the standard "static" attributes.
692        for (Attribute a : staticDSEAttributes)
693        {
694          AttributeType type = a.getAttributeType();
695    
696          if (type.isOperational() && (! showAllAttributes))
697          {
698            List<Attribute> attrs = dseOperationalAttrs.get(type);
699            if (attrs == null)
700            {
701              attrs = new ArrayList<Attribute>();
702              attrs.add(a);
703              dseOperationalAttrs.put(type, attrs);
704            }
705            else
706            {
707              attrs.add(a);
708            }
709          }
710          else
711          {
712            List<Attribute> attrs = dseUserAttrs.get(type);
713            if (attrs == null)
714            {
715              attrs = new ArrayList<Attribute>();
716              attrs.add(a);
717              dseUserAttrs.put(type, attrs);
718            }
719            else
720            {
721              attrs.add(a);
722            }
723          }
724        }
725    
726    
727        // Add all the user-defined attributes.
728        for (Attribute a : userDefinedAttributes)
729        {
730          AttributeType type = a.getAttributeType();
731    
732          if (type.isOperational() && (! showAllAttributes))
733          {
734            List<Attribute> attrs = dseOperationalAttrs.get(type);
735            if (attrs == null)
736            {
737              attrs = new ArrayList<Attribute>();
738              attrs.add(a);
739              dseOperationalAttrs.put(type, attrs);
740            }
741            else
742            {
743              attrs.add(a);
744            }
745          }
746          else
747          {
748            List<Attribute> attrs = dseUserAttrs.get(type);
749            if (attrs == null)
750            {
751              attrs = new ArrayList<Attribute>();
752              attrs.add(a);
753              dseUserAttrs.put(type, attrs);
754            }
755            else
756            {
757              attrs.add(a);
758            }
759          }
760        }
761    
762    
763        // Construct and return the entry.
764        Entry e = new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs,
765                            dseOperationalAttrs);
766        e.processVirtualAttributes();
767        return e;
768      }
769    
770    
771    
772      /**
773       * Creates an attribute for the root DSE with the following criteria.
774       *
775       * @param  name       The name for the attribute.
776       * @param  lowerName  The name for the attribute formatted in all lowercase
777       *                    characters.
778       * @param  value      The value to use for the attribute.
779       *
780       * @return  The constructed attribute.
781       */
782      private Attribute createAttribute(String name, String lowerName,
783                                        String value)
784      {
785        AttributeType type = DirectoryServer.getAttributeType(lowerName);
786        if (type == null)
787        {
788          type = DirectoryServer.getDefaultAttributeType(name);
789        }
790    
791        LinkedHashSet<AttributeValue> attrValues =
792             new LinkedHashSet<AttributeValue>(1);
793        attrValues.add(new AttributeValue(type, new ASN1OctetString(value)));
794    
795        return new Attribute(type, name, attrValues);
796      }
797    
798    
799    
800      /**
801       * Creates an attribute for the root DSE meant to hold a set of DNs.
802       *
803       * @param  name       The name for the attribute.
804       * @param  lowerName  The name for the attribute formatted in all lowercase
805       *                    characters.
806       * @param  values     The set of DN values to use for the attribute.
807       *
808       * @return  The constructed attribute.
809       */
810      private Attribute createDNAttribute(String name, String lowerName,
811                                          Collection<DN> values)
812      {
813        AttributeType type = DirectoryServer.getAttributeType(lowerName);
814        if (type == null)
815        {
816          type = DirectoryServer.getDefaultAttributeType(name);
817        }
818    
819        LinkedHashSet<AttributeValue> attrValues =
820             new LinkedHashSet<AttributeValue>();
821        for (DN dn : values)
822        {
823          attrValues.add(new AttributeValue(type,
824                                            new ASN1OctetString(dn.toString())));
825        }
826    
827        return new Attribute(type, name, attrValues);
828      }
829    
830    
831    
832      /**
833       * Creates an attribute for the root DSE with the following criteria.
834       *
835       * @param  name       The name for the attribute.
836       * @param  lowerName  The name for the attribute formatted in all lowercase
837       *                    characters.
838       * @param  values     The set of values to use for the attribute.
839       *
840       * @return  The constructed attribute.
841       */
842      private Attribute createAttribute(String name, String lowerName,
843                                        Collection<String> values)
844      {
845        AttributeType type = DirectoryServer.getAttributeType(lowerName);
846        if (type == null)
847        {
848          type = DirectoryServer.getDefaultAttributeType(name);
849        }
850    
851        LinkedHashSet<AttributeValue> attrValues =
852             new LinkedHashSet<AttributeValue>();
853        for (String s : values)
854        {
855          attrValues.add(new AttributeValue(type, new ASN1OctetString(s)));
856        }
857    
858        return new Attribute(type, name, attrValues);
859      }
860    
861    
862    
863      /**
864       * {@inheritDoc}
865       */
866      @Override()
867      public boolean entryExists(DN entryDN)
868             throws DirectoryException
869      {
870        // If the specified DN was the null DN, then it exists.
871        if (entryDN.isNullDN())
872        {
873          return true;
874        }
875    
876    
877        // If it was not the null DN, then iterate through the associated
878        // subordinate backends to make the determination.
879        Map<DN,Backend> baseMap;
880        if (subordinateBaseDNs == null)
881        {
882          baseMap = DirectoryServer.getPublicNamingContexts();
883        }
884        else
885        {
886          baseMap = subordinateBaseDNs;
887        }
888    
889        for (DN baseDN : baseMap.keySet())
890        {
891          if (entryDN.isDescendantOf(baseDN))
892          {
893            Backend b = baseMap.get(baseDN);
894            if (b.entryExists(entryDN))
895            {
896              return true;
897            }
898          }
899        }
900    
901        return false;
902      }
903    
904    
905    
906      /**
907       * {@inheritDoc}
908       */
909      @Override()
910      public void addEntry(Entry entry, AddOperation addOperation)
911             throws DirectoryException
912      {
913        Message message =
914            ERR_ROOTDSE_ADD_NOT_SUPPORTED.get(String.valueOf(entry.getDN()));
915        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
916      }
917    
918    
919    
920      /**
921       * {@inheritDoc}
922       */
923      @Override()
924      public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
925             throws DirectoryException
926      {
927        Message message =
928            ERR_ROOTDSE_DELETE_NOT_SUPPORTED.get(String.valueOf(entryDN));
929        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
930      }
931    
932    
933    
934      /**
935       * {@inheritDoc}
936       */
937      @Override()
938      public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
939             throws DirectoryException
940      {
941        Message message = ERR_ROOTDSE_MODIFY_NOT_SUPPORTED.get(
942            String.valueOf(entry.getDN()), String.valueOf(configEntryDN));
943        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
944      }
945    
946    
947    
948      /**
949       * {@inheritDoc}
950       */
951      @Override()
952      public void renameEntry(DN currentDN, Entry entry,
953                                       ModifyDNOperation modifyDNOperation)
954             throws DirectoryException
955      {
956        Message message =
957            ERR_ROOTDSE_MODIFY_DN_NOT_SUPPORTED.get(String.valueOf(currentDN));
958        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
959      }
960    
961    
962    
963      /**
964       * {@inheritDoc}
965       */
966      @Override()
967      public void search(SearchOperation searchOperation)
968             throws DirectoryException, CanceledOperationException {
969        DN baseDN = searchOperation.getBaseDN();
970        if (! baseDN.isNullDN())
971        {
972          Message message = ERR_ROOTDSE_INVALID_SEARCH_BASE.
973              get(searchOperation.getConnectionID(),
974                  searchOperation.getOperationID(), String.valueOf(baseDN));
975          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
976        }
977    
978    
979        SearchFilter filter = searchOperation.getFilter();
980        switch (searchOperation.getScope())
981        {
982          case BASE_OBJECT:
983            Entry dseEntry = getRootDSE();
984            if (filter.matchesEntry(dseEntry))
985            {
986              searchOperation.returnEntry(dseEntry, null);
987            }
988            break;
989    
990    
991          case SINGLE_LEVEL:
992            Map<DN,Backend> baseMap;
993            if (subordinateBaseDNs == null)
994            {
995              baseMap = DirectoryServer.getPublicNamingContexts();
996            }
997            else
998            {
999              baseMap = subordinateBaseDNs;
1000            }
1001    
1002            for (DN subBase : baseMap.keySet())
1003            {
1004              searchOperation.checkIfCanceled(false);
1005    
1006              Backend b = baseMap.get(subBase);
1007              Entry subBaseEntry = b.getEntry(subBase);
1008              if ((subBaseEntry != null) && filter.matchesEntry(subBaseEntry))
1009              {
1010                searchOperation.returnEntry(subBaseEntry, null);
1011              }
1012            }
1013            break;
1014    
1015    
1016          case WHOLE_SUBTREE:
1017          case SUBORDINATE_SUBTREE:
1018            if (subordinateBaseDNs == null)
1019            {
1020              baseMap = DirectoryServer.getPublicNamingContexts();
1021            }
1022            else
1023            {
1024              baseMap = subordinateBaseDNs;
1025            }
1026    
1027            try
1028            {
1029              for (DN subBase : baseMap.keySet())
1030              {
1031                searchOperation.checkIfCanceled(false);
1032    
1033                Backend b = baseMap.get(subBase);
1034                searchOperation.setBaseDN(subBase);
1035    
1036                try
1037                {
1038                  b.search(searchOperation);
1039                }
1040                catch (DirectoryException de)
1041                {
1042                  // If it's a "no such object" exception, then the base entry for
1043                  // the backend doesn't exist.  This isn't an error, so ignore it.
1044                  // We'll propogate all other errors, though.
1045                  if (de.getResultCode() != ResultCode.NO_SUCH_OBJECT)
1046                  {
1047                    throw de;
1048                  }
1049                }
1050              }
1051            }
1052            catch (DirectoryException de)
1053            {
1054              if (debugEnabled())
1055              {
1056                TRACER.debugCaught(DebugLogLevel.ERROR, de);
1057              }
1058    
1059              throw de;
1060            }
1061            catch (Exception e)
1062            {
1063              if (debugEnabled())
1064              {
1065                TRACER.debugCaught(DebugLogLevel.ERROR, e);
1066              }
1067    
1068              Message message = ERR_ROOTDSE_UNEXPECTED_SEARCH_FAILURE.
1069                  get(searchOperation.getConnectionID(),
1070                      searchOperation.getOperationID(),
1071                      stackTraceToSingleLineString(e));
1072              throw new DirectoryException(
1073                             DirectoryServer.getServerErrorResultCode(), message,
1074                             e);
1075            }
1076            finally
1077            {
1078              searchOperation.setBaseDN(rootDSEDN);
1079            }
1080            break;
1081    
1082          default:
1083            Message message = ERR_ROOTDSE_INVALID_SEARCH_SCOPE.
1084                get(searchOperation.getConnectionID(),
1085                    searchOperation.getOperationID(),
1086                    String.valueOf(searchOperation.getScope()));
1087            throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
1088        }
1089      }
1090    
1091    
1092    
1093      /**
1094       * {@inheritDoc}
1095       */
1096      @Override()
1097      public HashSet<String> getSupportedControls()
1098      {
1099        return supportedControls;
1100      }
1101    
1102    
1103    
1104      /**
1105       * {@inheritDoc}
1106       */
1107      @Override()
1108      public HashSet<String> getSupportedFeatures()
1109      {
1110        return supportedFeatures;
1111      }
1112    
1113    
1114    
1115      /**
1116       * {@inheritDoc}
1117       */
1118      @Override()
1119      public boolean supportsLDIFExport()
1120      {
1121        // We will only export the DSE entry itself.
1122        return true;
1123      }
1124    
1125    
1126    
1127      /**
1128       * {@inheritDoc}
1129       */
1130      @Override()
1131      public void exportLDIF(LDIFExportConfig exportConfig)
1132             throws DirectoryException
1133      {
1134        // Create the LDIF writer.
1135        LDIFWriter ldifWriter;
1136        try
1137        {
1138          ldifWriter = new LDIFWriter(exportConfig);
1139        }
1140        catch (Exception e)
1141        {
1142          if (debugEnabled())
1143          {
1144            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1145          }
1146    
1147          Message message = ERR_ROOTDSE_UNABLE_TO_CREATE_LDIF_WRITER.get(
1148              stackTraceToSingleLineString(e));
1149          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1150                                       message);
1151        }
1152    
1153    
1154        // Write the root DSE entry itself to it.  Make sure to close the LDIF
1155        // writer when we're done.
1156        try
1157        {
1158          ldifWriter.writeEntry(getRootDSE());
1159        }
1160        catch (Exception e)
1161        {
1162          if (debugEnabled())
1163          {
1164            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1165          }
1166    
1167          Message message =
1168              ERR_ROOTDSE_UNABLE_TO_EXPORT_DSE.get(stackTraceToSingleLineString(e));
1169          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1170                                       message);
1171        }
1172        finally
1173        {
1174          try
1175          {
1176            ldifWriter.close();
1177          }
1178          catch (Exception e)
1179          {
1180            if (debugEnabled())
1181            {
1182              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1183            }
1184          }
1185        }
1186      }
1187    
1188    
1189    
1190      /**
1191       * {@inheritDoc}
1192       */
1193      @Override()
1194      public boolean supportsLDIFImport()
1195      {
1196        // This backend does not support LDIF imports.
1197        return false;
1198      }
1199    
1200    
1201    
1202      /**
1203       * {@inheritDoc}
1204       */
1205      @Override()
1206      public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
1207             throws DirectoryException
1208      {
1209        // This backend does not support LDIF imports.
1210        Message message = ERR_ROOTDSE_IMPORT_NOT_SUPPORTED.get();
1211        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1212      }
1213    
1214    
1215    
1216      /**
1217       * {@inheritDoc}
1218       */
1219      @Override()
1220      public boolean supportsBackup()
1221      {
1222        // This backend does not provide a backup/restore mechanism.
1223        return false;
1224      }
1225    
1226    
1227    
1228      /**
1229       * {@inheritDoc}
1230       */
1231      @Override()
1232      public boolean supportsBackup(BackupConfig backupConfig,
1233                                    StringBuilder unsupportedReason)
1234      {
1235        // This backend does not provide a backup/restore mechanism.
1236        return false;
1237      }
1238    
1239    
1240    
1241      /**
1242       * {@inheritDoc}
1243       */
1244      @Override()
1245      public void createBackup(BackupConfig backupConfig)
1246             throws DirectoryException
1247      {
1248        // This backend does not provide a backup/restore mechanism.
1249        Message message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
1250        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1251      }
1252    
1253    
1254    
1255      /**
1256       * {@inheritDoc}
1257       */
1258      @Override()
1259      public void removeBackup(BackupDirectory backupDirectory,
1260                               String backupID)
1261             throws DirectoryException
1262      {
1263        // This backend does not provide a backup/restore mechanism.
1264        Message message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
1265        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1266      }
1267    
1268    
1269    
1270      /**
1271       * {@inheritDoc}
1272       */
1273      @Override()
1274      public boolean supportsRestore()
1275      {
1276        // This backend does not provide a backup/restore mechanism.
1277        return false;
1278      }
1279    
1280    
1281    
1282      /**
1283       * {@inheritDoc}
1284       */
1285      @Override()
1286      public void restoreBackup(RestoreConfig restoreConfig)
1287             throws DirectoryException
1288      {
1289        // This backend does not provide a backup/restore mechanism.
1290        Message message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
1291        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1292      }
1293    
1294    
1295    
1296      /**
1297       * {@inheritDoc}
1298       */
1299      @Override()
1300      public boolean isConfigurationAcceptable(Configuration configuration,
1301                                               List<Message> unacceptableReasons)
1302      {
1303        RootDSEBackendCfg config = (RootDSEBackendCfg) configuration;
1304        return isConfigurationChangeAcceptable(config, unacceptableReasons);
1305      }
1306    
1307    
1308    
1309      /**
1310       * {@inheritDoc}
1311       */
1312      public boolean isConfigurationChangeAcceptable(
1313           RootDSEBackendCfg cfg,
1314           List<Message> unacceptableReasons)
1315      {
1316        boolean configIsAcceptable = true;
1317    
1318    
1319        try
1320        {
1321          Set<DN> subDNs = cfg.getSubordinateBaseDN();
1322          if (subDNs.isEmpty())
1323          {
1324            // This is fine -- we'll just use the set of user-defined suffixes.
1325          }
1326          else
1327          {
1328            for (DN baseDN : subDNs)
1329            {
1330              Backend backend = DirectoryServer.getBackend(baseDN);
1331              if (backend == null)
1332              {
1333                Message message = WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(
1334                        String.valueOf(baseDN));
1335                unacceptableReasons.add(message);
1336                configIsAcceptable = false;
1337              }
1338            }
1339          }
1340        }
1341        catch (Exception e)
1342        {
1343          if (debugEnabled())
1344          {
1345            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1346          }
1347    
1348          Message message = WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get(
1349                  stackTraceToSingleLineString(e));
1350          unacceptableReasons.add(message);
1351          configIsAcceptable = false;
1352        }
1353    
1354    
1355        return configIsAcceptable;
1356      }
1357    
1358    
1359    
1360      /**
1361       * {@inheritDoc}
1362       */
1363      public ConfigChangeResult applyConfigurationChange(RootDSEBackendCfg cfg)
1364      {
1365        ResultCode         resultCode          = ResultCode.SUCCESS;
1366        boolean            adminActionRequired = false;
1367        ArrayList<Message> messages            = new ArrayList<Message>();
1368    
1369    
1370        // Check to see if we should apply a new set of base DNs.
1371        ConcurrentHashMap<DN,Backend> subBases;
1372        try
1373        {
1374          Set<DN> subDNs = cfg.getSubordinateBaseDN();
1375          if (subDNs.isEmpty())
1376          {
1377            // This is fine -- we'll just use the set of user-defined suffixes.
1378            subBases = null;
1379          }
1380          else
1381          {
1382            subBases = new ConcurrentHashMap<DN,Backend>();
1383            for (DN baseDN : subDNs)
1384            {
1385              Backend backend = DirectoryServer.getBackend(baseDN);
1386              if (backend == null)
1387              {
1388                // This is not fine.  We can't use a suffix that doesn't exist.
1389                Message message = WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(
1390                        String.valueOf(baseDN));
1391                messages.add(message);
1392    
1393                if (resultCode == ResultCode.SUCCESS)
1394                {
1395                  resultCode = DirectoryServer.getServerErrorResultCode();
1396                }
1397              }
1398              else
1399              {
1400                subBases.put(baseDN, backend);
1401              }
1402            }
1403          }
1404        }
1405        catch (Exception e)
1406        {
1407          if (debugEnabled())
1408          {
1409            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1410          }
1411    
1412          Message message = WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get(
1413                  stackTraceToSingleLineString(e));
1414          messages.add(message);
1415    
1416          if (resultCode == ResultCode.SUCCESS)
1417          {
1418            resultCode = DirectoryServer.getServerErrorResultCode();
1419          }
1420    
1421          subBases = null;
1422        }
1423    
1424    
1425        boolean newShowAll = cfg.isShowAllAttributes();
1426    
1427    
1428        // Check to see if there is a new set of user-defined attributes.
1429        ArrayList<Attribute> userAttrs = new ArrayList<Attribute>();
1430        try
1431        {
1432          ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN);
1433    
1434          for (List<Attribute> attrs :
1435               configEntry.getEntry().getUserAttributes().values())
1436          {
1437            for (Attribute a : attrs)
1438            {
1439              if (! isDSEConfigAttribute(a))
1440              {
1441                userAttrs.add(a);
1442              }
1443            }
1444          }
1445          for (List<Attribute> attrs :
1446               configEntry.getEntry().getOperationalAttributes().values())
1447          {
1448            for (Attribute a : attrs)
1449            {
1450              if (! isDSEConfigAttribute(a))
1451              {
1452                userAttrs.add(a);
1453              }
1454            }
1455          }
1456        }
1457        catch (ConfigException e)
1458        {
1459          if (debugEnabled())
1460          {
1461            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1462          }
1463    
1464          messages.add(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get(
1465                  String.valueOf(configEntryDN),
1466                  stackTraceToSingleLineString(e)));
1467          resultCode = DirectoryServer.getServerErrorResultCode();
1468        }
1469    
1470    
1471        if (resultCode == ResultCode.SUCCESS)
1472        {
1473          subordinateBaseDNs = subBases;
1474    
1475          if (subordinateBaseDNs == null)
1476          {
1477            Message message = INFO_ROOTDSE_USING_SUFFIXES_AS_BASE_DNS.get();
1478            messages.add(message);
1479          }
1480          else
1481          {
1482            StringBuilder basesStr = new StringBuilder();
1483            for (DN dn : subordinateBaseDNs.keySet())
1484            {
1485              if (basesStr.length() > 0)
1486              {
1487                basesStr.append(", ");
1488              }
1489              else
1490              {
1491                basesStr.append("{ ");
1492              }
1493    
1494              basesStr.append(dn);
1495            }
1496    
1497            basesStr.append(" }");
1498    
1499            Message message = INFO_ROOTDSE_USING_NEW_SUBORDINATE_BASE_DNS.get(
1500                    basesStr.toString());
1501            messages.add(message);
1502          }
1503    
1504    
1505          if (showAllAttributes != newShowAll)
1506          {
1507            showAllAttributes = newShowAll;
1508            Message message = INFO_ROOTDSE_UPDATED_SHOW_ALL_ATTRS.get(
1509                    ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES,
1510                    String.valueOf(showAllAttributes));
1511            messages.add(message);
1512          }
1513    
1514    
1515          userDefinedAttributes = userAttrs;
1516          Message message = INFO_ROOTDSE_USING_NEW_USER_ATTRS.get();
1517          messages.add(message);
1518        }
1519    
1520    
1521        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
1522      }
1523    
1524    
1525    
1526      /**
1527       * {@inheritDoc}
1528       */
1529      public void preloadEntryCache() throws UnsupportedOperationException {
1530        throw new UnsupportedOperationException("Operation not supported.");
1531      }
1532    }
1533