001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.backends;
028
029import static org.forgerock.util.Reject.*;
030import static org.opends.messages.BackendMessages.*;
031import static org.opends.messages.ConfigMessages.*;
032import static org.opends.server.config.ConfigConstants.*;
033import static org.opends.server.util.CollectionUtils.*;
034import static org.opends.server.util.ServerConstants.*;
035import static org.opends.server.util.StaticUtils.*;
036
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.HashMap;
042import java.util.List;
043import java.util.Map;
044import java.util.Set;
045import java.util.TreeSet;
046import java.util.concurrent.ConcurrentHashMap;
047
048import javax.net.ssl.SSLContext;
049import javax.net.ssl.SSLParameters;
050
051import org.forgerock.i18n.LocalizableMessage;
052import org.forgerock.i18n.slf4j.LocalizedLogger;
053import org.forgerock.opendj.config.server.ConfigChangeResult;
054import org.forgerock.opendj.config.server.ConfigException;
055import org.forgerock.opendj.ldap.ConditionResult;
056import org.forgerock.opendj.ldap.ResultCode;
057import org.forgerock.util.Reject;
058import org.forgerock.util.Utils;
059import org.opends.server.admin.server.ConfigurationChangeListener;
060import org.opends.server.admin.std.server.RootDSEBackendCfg;
061import org.opends.server.api.Backend;
062import org.opends.server.api.ClientConnection;
063import org.opends.server.config.ConfigEntry;
064import org.opends.server.core.AddOperation;
065import org.opends.server.core.DeleteOperation;
066import org.opends.server.core.DirectoryServer;
067import org.opends.server.core.ModifyDNOperation;
068import org.opends.server.core.ModifyOperation;
069import org.opends.server.core.SearchOperation;
070import org.opends.server.core.ServerContext;
071import org.opends.server.types.*;
072import org.opends.server.util.BuildVersion;
073import org.opends.server.util.LDIFWriter;
074
075/**
076 * This class defines a backend to hold the Directory Server root DSE.  It is a
077 * kind of meta-backend in that it will dynamically generate the root DSE entry
078 * (although there will be some caching) for base-level searches, and will
079 * simply redirect to other backends for operations in other scopes.
080 * <BR><BR>
081 * This should not be treated like a regular backend when it comes to
082 * initializing the server configuration.  It should only be initialized after
083 * all other backends are configured.  As such, it should have a special entry
084 * in the configuration rather than being placed under the cn=Backends branch
085 * with the other backends.
086 */
087public class RootDSEBackend
088       extends Backend<RootDSEBackendCfg>
089       implements ConfigurationChangeListener<RootDSEBackendCfg>
090{
091  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
092
093  /**
094   * The set of standard "static" attributes that we will always include in the
095   * root DSE entry and won't change while the server is running.
096   */
097  private List<Attribute> staticDSEAttributes;
098  /** The set of user-defined attributes that will be included in the root DSE entry. */
099  private List<Attribute> userDefinedAttributes;
100  /**
101   * Indicates whether the attributes of the root DSE should always be treated
102   * as user attributes even if they are defined as operational in the schema.
103   */
104  private boolean showAllAttributes;
105
106  /** The set of objectclasses that will be used in the root DSE entry. */
107  private Map<ObjectClass, String> dseObjectClasses;
108
109  /** The current configuration state. */
110  private RootDSEBackendCfg currentConfig;
111  /** The DN of the configuration entry for this backend. */
112  private DN configEntryDN;
113
114  /** The DN for the root DSE. */
115  private DN rootDSEDN;
116  /** The set of base DNs for this backend. */
117  private DN[] baseDNs;
118  /**
119   * The set of subordinate base DNs and their associated backends that will be
120   * used for non-base searches.
121   */
122  private ConcurrentHashMap<DN, Backend<?>> subordinateBaseDNs;
123
124
125
126  /**
127   * Creates a new backend with the provided information.  All backend
128   * implementations must implement a default constructor that use
129   * <CODE>super()</CODE> to invoke this constructor.
130   */
131  public RootDSEBackend()
132  {
133    super();
134
135    // Perform all initialization in initializeBackend.
136  }
137
138  @Override
139  public void configureBackend(RootDSEBackendCfg config, ServerContext serverContext) throws ConfigException
140  {
141    Reject.ifNull(config);
142    currentConfig = config;
143    configEntryDN = config.dn();
144  }
145
146  @Override
147  public void openBackend() throws ConfigException, InitializationException
148  {
149    ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN);
150
151    // Make sure that a configuration entry was provided.  If not, then we will
152    // not be able to complete initialization.
153    if (configEntry == null)
154    {
155      LocalizableMessage message = ERR_ROOTDSE_CONFIG_ENTRY_NULL.get();
156      throw new ConfigException(message);
157    }
158
159    userDefinedAttributes = new ArrayList<>();
160    addAllUserDefinedAttrs(userDefinedAttributes, configEntry.getEntry());
161
162
163    // Create the set of base DNs that we will handle.  In this case, it's just
164    // the root DSE.
165    rootDSEDN    = DN.rootDN();
166    this.baseDNs = new DN[] { rootDSEDN };
167
168
169    // Create the set of subordinate base DNs.  If this is specified in the
170    // configuration, then use that set.  Otherwise, use the set of non-private
171    // backends defined in the server.
172    try
173    {
174      Set<DN> subDNs = currentConfig.getSubordinateBaseDN();
175      if (subDNs.isEmpty())
176      {
177        // This is fine -- we'll just use the set of user-defined suffixes.
178        subordinateBaseDNs = null;
179      }
180      else
181      {
182        subordinateBaseDNs = new ConcurrentHashMap<>();
183        for (DN baseDN : subDNs)
184        {
185          Backend<?> backend = DirectoryServer.getBackend(baseDN);
186          if (backend == null)
187          {
188            logger.warn(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE, baseDN);
189          }
190          else
191          {
192            subordinateBaseDNs.put(baseDN, backend);
193          }
194        }
195      }
196    }
197    catch (Exception e)
198    {
199      logger.traceException(e);
200
201      LocalizableMessage message = WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get(
202          stackTraceToSingleLineString(e));
203      throw new InitializationException(message, e);
204    }
205
206
207    // Determine whether all root DSE attributes should be treated as user
208    // attributes.
209    showAllAttributes = currentConfig.isShowAllAttributes();
210
211
212    // Construct the set of "static" attributes that will always be present in
213    // the root DSE.
214    staticDSEAttributes = new ArrayList<>();
215    staticDSEAttributes.add(Attributes.create(ATTR_VENDOR_NAME, SERVER_VENDOR_NAME));
216    staticDSEAttributes.add(Attributes.create(ATTR_VENDOR_VERSION,
217                                 DirectoryServer.getVersionString()));
218    staticDSEAttributes.add(Attributes.create("fullVendorVersion",
219                                 BuildVersion.binaryVersion().toString()));
220
221    // Construct the set of objectclasses to include in the root DSE entry.
222    dseObjectClasses = new HashMap<>(2);
223    ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP);
224    if (topOC == null)
225    {
226      topOC = DirectoryServer.getDefaultObjectClass(OC_TOP);
227    }
228    dseObjectClasses.put(topOC, OC_TOP);
229
230    ObjectClass rootDSEOC = DirectoryServer.getObjectClass(OC_ROOT_DSE);
231    if (rootDSEOC == null)
232    {
233      rootDSEOC = DirectoryServer.getDefaultObjectClass(OC_ROOT_DSE);
234    }
235    dseObjectClasses.put(rootDSEOC, OC_ROOT_DSE);
236
237
238    // Set the backend ID for this backend. The identifier needs to be
239    // specific enough to avoid conflict with user backend identifiers.
240    setBackendID("__root.dse__");
241
242
243    // Register as a change listener.
244    currentConfig.addChangeListener(this);
245  }
246
247  /**
248   * Get the set of user-defined attributes for the configuration entry. Any
249   * attributes that we do not recognize will be included directly in the root DSE.
250   */
251  private void addAllUserDefinedAttrs(List<Attribute> userDefinedAttrs, Entry configEntry)
252  {
253    for (List<Attribute> attrs : configEntry.getUserAttributes().values())
254    {
255      for (Attribute a : attrs)
256      {
257        if (!isDSEConfigAttribute(a))
258        {
259          userDefinedAttrs.add(a);
260        }
261      }
262    }
263    for (List<Attribute> attrs : configEntry.getOperationalAttributes().values())
264    {
265      for (Attribute a : attrs)
266      {
267        if (!isDSEConfigAttribute(a))
268        {
269          userDefinedAttrs.add(a);
270        }
271      }
272    }
273  }
274
275  @Override
276  public void closeBackend()
277  {
278    currentConfig.removeChangeListener(this);
279  }
280
281
282
283  /**
284   * Indicates whether the provided attribute is one that is used in the
285   * configuration of this backend.
286   *
287   * @param  attribute  The attribute for which to make the determination.
288   *
289   * @return  <CODE>true</CODE> if the provided attribute is one that is used in
290   *          the configuration of this backend, <CODE>false</CODE> if not.
291   */
292  private boolean isDSEConfigAttribute(Attribute attribute)
293  {
294    AttributeType attrType = attribute.getAttributeType();
295    return attrType.hasName(ATTR_ROOT_DSE_SUBORDINATE_BASE_DN.toLowerCase())
296        || attrType.hasName(ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES.toLowerCase())
297        || attrType.hasName(ATTR_COMMON_NAME);
298  }
299
300  @Override
301  public DN[] getBaseDNs()
302  {
303    return baseDNs;
304  }
305
306  @Override
307  public synchronized long getEntryCount()
308  {
309    // There is always just a single entry in this backend.
310    return 1;
311  }
312
313  @Override
314  public boolean isIndexed(AttributeType attributeType, IndexType indexType)
315  {
316    // All searches in this backend will always be considered indexed.
317    return true;
318  }
319
320  @Override
321  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
322  {
323    final long ret = getNumberOfChildren(entryDN);
324    if(ret < 0)
325    {
326      return ConditionResult.UNDEFINED;
327    }
328    return ConditionResult.valueOf(ret != 0);
329  }
330
331  @Override
332  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
333  {
334    checkNotNull(baseDN, "baseDN must not be null");
335    if (!baseDN.isRootDN())
336    {
337      return -1;
338    }
339
340    long count = 1;
341    for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
342    {
343      DN subBase = entry.getKey();
344      Backend<?> b = entry.getValue();
345      Entry subBaseEntry = b.getEntry(subBase);
346      if (subBaseEntry != null)
347      {
348        count++;
349        count += b.getNumberOfEntriesInBaseDN(subBase);
350      }
351    }
352
353    return count;
354  }
355
356  @Override
357  public long getNumberOfChildren(DN parentDN) throws DirectoryException
358  {
359    checkNotNull(parentDN, "parentDN must not be null");
360    if (!parentDN.isRootDN())
361    {
362      return -1;
363    }
364
365    long count = 0;
366
367    for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
368    {
369      DN subBase = entry.getKey();
370      Entry subBaseEntry = entry.getValue().getEntry(subBase);
371      if (subBaseEntry != null)
372      {
373        count ++;
374      }
375    }
376
377    return count;
378  }
379
380  @Override
381  public Entry getEntry(DN entryDN) throws DirectoryException
382  {
383    // If the requested entry was the root DSE, then create and return it.
384    if (entryDN == null || entryDN.isRootDN())
385    {
386      return getRootDSE();
387    }
388
389
390    // This method should never be used to get anything other than the root DSE.
391    // If we got here, then that appears to be the case, so log a message.
392    logger.warn(WARN_ROOTDSE_GET_ENTRY_NONROOT, entryDN);
393
394
395    // Go ahead and check the subordinate backends to see if we can find the
396    // entry there.  Note that in order to avoid potential loop conditions, this
397    // will only work if the set of subordinate bases has been explicitly
398    // specified.
399    if (subordinateBaseDNs != null)
400    {
401      for (Backend<?> b : subordinateBaseDNs.values())
402      {
403        if (b.handlesEntry(entryDN))
404        {
405          return b.getEntry(entryDN);
406        }
407      }
408    }
409
410
411    // If we've gotten here, then we couldn't find the entry so return null.
412    return null;
413  }
414
415
416
417  /**
418   * Retrieves the root DSE entry for the Directory Server.
419   *
420   * @return The root DSE entry for the Directory Server.
421   */
422  public Entry getRootDSE()
423  {
424    return getRootDSE(null);
425  }
426
427
428
429  /**
430   * Retrieves the root DSE entry for the Directory Server.
431   *
432   * @param connection
433   *          The client connection, or {@code null} if there is no associated
434   *          client connection.
435   * @return The root DSE entry for the Directory Server.
436   */
437  private Entry getRootDSE(ClientConnection connection)
438  {
439    Map<AttributeType, List<Attribute>> dseUserAttrs = new HashMap<>();
440    Map<AttributeType, List<Attribute>> dseOperationalAttrs = new HashMap<>();
441
442    Attribute publicNamingContextAttr = createAttribute(
443        ATTR_NAMING_CONTEXTS, ATTR_NAMING_CONTEXTS_LC,
444        DirectoryServer.getPublicNamingContexts().keySet());
445    addAttribute(publicNamingContextAttr, dseUserAttrs, dseOperationalAttrs);
446
447    // Add the "ds-private-naming-contexts" attribute.
448    Attribute privateNamingContextAttr = createAttribute(
449        ATTR_PRIVATE_NAMING_CONTEXTS, ATTR_PRIVATE_NAMING_CONTEXTS,
450        DirectoryServer.getPrivateNamingContexts().keySet());
451    addAttribute(privateNamingContextAttr, dseUserAttrs, dseOperationalAttrs);
452
453    // Add the "supportedControl" attribute.
454    Attribute supportedControlAttr = createAttribute(ATTR_SUPPORTED_CONTROL,
455        ATTR_SUPPORTED_CONTROL_LC, DirectoryServer.getSupportedControls());
456    addAttribute(supportedControlAttr, dseUserAttrs, dseOperationalAttrs);
457
458    // Add the "supportedExtension" attribute.
459    Attribute supportedExtensionAttr = createAttribute(
460        ATTR_SUPPORTED_EXTENSION, ATTR_SUPPORTED_EXTENSION_LC, DirectoryServer
461            .getSupportedExtensions().keySet());
462    addAttribute(supportedExtensionAttr, dseUserAttrs, dseOperationalAttrs);
463
464    // Add the "supportedFeature" attribute.
465    Attribute supportedFeatureAttr = createAttribute(ATTR_SUPPORTED_FEATURE,
466        ATTR_SUPPORTED_FEATURE_LC, DirectoryServer.getSupportedFeatures());
467    addAttribute(supportedFeatureAttr, dseUserAttrs, dseOperationalAttrs);
468
469    // Add the "supportedSASLMechanisms" attribute.
470    Attribute supportedSASLMechAttr = createAttribute(
471        ATTR_SUPPORTED_SASL_MECHANISMS, ATTR_SUPPORTED_SASL_MECHANISMS_LC,
472        DirectoryServer.getSupportedSASLMechanisms().keySet());
473    addAttribute(supportedSASLMechAttr, dseUserAttrs, dseOperationalAttrs);
474
475    // Add the "supportedLDAPVersions" attribute.
476    TreeSet<String> versionStrings = new TreeSet<>();
477    for (Integer ldapVersion : DirectoryServer.getSupportedLDAPVersions())
478    {
479      versionStrings.add(ldapVersion.toString());
480    }
481    Attribute supportedLDAPVersionAttr = createAttribute(
482        ATTR_SUPPORTED_LDAP_VERSION, ATTR_SUPPORTED_LDAP_VERSION_LC, versionStrings);
483    addAttribute(supportedLDAPVersionAttr, dseUserAttrs, dseOperationalAttrs);
484
485    // Add the "supportedAuthPasswordSchemes" attribute.
486    Attribute supportedAuthPWSchemesAttr = createAttribute(
487        ATTR_SUPPORTED_AUTH_PW_SCHEMES, ATTR_SUPPORTED_AUTH_PW_SCHEMES_LC,
488        DirectoryServer.getAuthPasswordStorageSchemes().keySet());
489    addAttribute(supportedAuthPWSchemesAttr, dseUserAttrs, dseOperationalAttrs);
490
491
492    // Obtain TLS protocol and cipher support.
493    Collection<String> supportedTlsProtocols;
494    Collection<String> supportedTlsCiphers;
495    if (connection != null)
496    {
497      // Only return the list of enabled protocols / ciphers for the connection
498      // handler to which the client is connected.
499      supportedTlsProtocols = connection.getConnectionHandler().getEnabledSSLProtocols();
500      supportedTlsCiphers = connection.getConnectionHandler().getEnabledSSLCipherSuites();
501    }
502    else
503    {
504      try
505      {
506        final SSLContext context = SSLContext.getDefault();
507        final SSLParameters parameters = context.getSupportedSSLParameters();
508        supportedTlsProtocols = Arrays.asList(parameters.getProtocols());
509        supportedTlsCiphers = Arrays.asList(parameters.getCipherSuites());
510      }
511      catch (Exception e)
512      {
513        // A default SSL context should always be available.
514        supportedTlsProtocols = Collections.emptyList();
515        supportedTlsCiphers = Collections.emptyList();
516      }
517    }
518
519    // Add the "supportedTLSProtocols" attribute.
520    Attribute supportedTLSProtocolsAttr = createAttribute(
521        ATTR_SUPPORTED_TLS_PROTOCOLS, ATTR_SUPPORTED_TLS_PROTOCOLS_LC,
522        supportedTlsProtocols);
523    addAttribute(supportedTLSProtocolsAttr, dseUserAttrs, dseOperationalAttrs);
524
525    // Add the "supportedTLSCiphers" attribute.
526    Attribute supportedTLSCiphersAttr = createAttribute(
527        ATTR_SUPPORTED_TLS_CIPHERS, ATTR_SUPPORTED_TLS_CIPHERS_LC,
528        supportedTlsCiphers);
529    addAttribute(supportedTLSCiphersAttr, dseUserAttrs, dseOperationalAttrs);
530
531    addAll(staticDSEAttributes, dseUserAttrs, dseOperationalAttrs);
532    addAll(userDefinedAttributes, dseUserAttrs, dseOperationalAttrs);
533
534    // Construct and return the entry.
535    Entry e = new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs,
536                        dseOperationalAttrs);
537    e.processVirtualAttributes();
538    return e;
539  }
540
541  private void addAll(Collection<Attribute> attributes,
542      Map<AttributeType, List<Attribute>> userAttrs, Map<AttributeType, List<Attribute>> operationalAttrs)
543  {
544    for (Attribute a : attributes)
545    {
546      AttributeType type = a.getAttributeType();
547
548      final Map<AttributeType, List<Attribute>> attrsMap = type.isOperational() && !showAllAttributes
549          ? operationalAttrs
550          : userAttrs;
551      List<Attribute> attrs = attrsMap.get(type);
552      if (attrs == null)
553      {
554        attrs = new ArrayList<>();
555        attrsMap.put(type, attrs);
556      }
557      attrs.add(a);
558    }
559  }
560
561  private void addAttribute(Attribute attribute,
562      Map<AttributeType, List<Attribute>> userAttrs,
563      Map<AttributeType, List<Attribute>> operationalAttrs)
564  {
565    if (!attribute.isEmpty())
566    {
567      List<Attribute> attrs = newArrayList(attribute);
568      final AttributeType attrType = attribute.getAttributeType();
569      if (showAllAttributes || !attrType.isOperational())
570      {
571        userAttrs.put(attrType, attrs);
572      }
573      else
574      {
575        operationalAttrs.put(attrType, attrs);
576      }
577    }
578  }
579
580  /**
581   * Creates an attribute for the root DSE with the following
582   * criteria.
583   *
584   * @param name
585   *          The name for the attribute.
586   * @param lowerName
587   *          The name for the attribute formatted in all lowercase
588   *          characters.
589   * @param values
590   *          The set of values to use for the attribute.
591   * @return The constructed attribute.
592   */
593  private Attribute createAttribute(String name, String lowerName,
594                                    Collection<? extends Object> values)
595  {
596    AttributeType type = DirectoryServer.getAttributeTypeOrDefault(lowerName, name);
597
598    AttributeBuilder builder = new AttributeBuilder(type, name);
599    builder.addAllStrings(values);
600    return builder.toAttribute();
601  }
602
603  @Override
604  public boolean entryExists(DN entryDN) throws DirectoryException
605  {
606    // If the specified DN was the null DN, then it exists.
607    if (entryDN.isRootDN())
608    {
609      return true;
610    }
611
612
613    // If it was not the null DN, then iterate through the associated
614    // subordinate backends to make the determination.
615    for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
616    {
617      DN baseDN = entry.getKey();
618      if (entryDN.isDescendantOf(baseDN))
619      {
620        Backend<?> b = entry.getValue();
621        if (b.entryExists(entryDN))
622        {
623          return true;
624        }
625      }
626    }
627
628    return false;
629  }
630
631  @Override
632  public void addEntry(Entry entry, AddOperation addOperation) throws DirectoryException
633  {
634    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
635        ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID()));
636  }
637
638  @Override
639  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) throws DirectoryException
640  {
641    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
642        ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID()));
643  }
644
645  @Override
646  public void replaceEntry(Entry oldEntry, Entry newEntry,
647      ModifyOperation modifyOperation) throws DirectoryException
648  {
649    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
650        ERR_ROOTDSE_MODIFY_NOT_SUPPORTED.get(newEntry.getName(), configEntryDN));
651  }
652
653  @Override
654  public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation)
655         throws DirectoryException
656  {
657    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
658        ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID()));
659  }
660
661  @Override
662  public void search(SearchOperation searchOperation)
663         throws DirectoryException, CanceledOperationException {
664    DN baseDN = searchOperation.getBaseDN();
665    if (! baseDN.isRootDN())
666    {
667      LocalizableMessage message = ERR_ROOTDSE_INVALID_SEARCH_BASE.
668          get(searchOperation.getConnectionID(), searchOperation.getOperationID(), baseDN);
669      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
670    }
671
672
673    SearchFilter filter = searchOperation.getFilter();
674    switch (searchOperation.getScope().asEnum())
675    {
676      case BASE_OBJECT:
677        Entry dseEntry = getRootDSE(searchOperation.getClientConnection());
678        if (filter.matchesEntry(dseEntry))
679        {
680          searchOperation.returnEntry(dseEntry, null);
681        }
682        break;
683
684
685      case SINGLE_LEVEL:
686        for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
687        {
688          searchOperation.checkIfCanceled(false);
689
690          DN subBase = entry.getKey();
691          Backend<?> b = entry.getValue();
692          Entry subBaseEntry = b.getEntry(subBase);
693          if (subBaseEntry != null && filter.matchesEntry(subBaseEntry))
694          {
695            searchOperation.returnEntry(subBaseEntry, null);
696          }
697        }
698        break;
699
700
701      case WHOLE_SUBTREE:
702      case SUBORDINATES:
703        try
704        {
705          for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet())
706          {
707            searchOperation.checkIfCanceled(false);
708
709            DN subBase = entry.getKey();
710            Backend<?> b = entry.getValue();
711
712            searchOperation.setBaseDN(subBase);
713            try
714            {
715              b.search(searchOperation);
716            }
717            catch (DirectoryException de)
718            {
719              // If it's a "no such object" exception, then the base entry for
720              // the backend doesn't exist.  This isn't an error, so ignore it.
721              // We'll propogate all other errors, though.
722              if (de.getResultCode() != ResultCode.NO_SUCH_OBJECT)
723              {
724                throw de;
725              }
726            }
727          }
728        }
729        catch (DirectoryException de)
730        {
731          logger.traceException(de);
732
733          throw de;
734        }
735        catch (Exception e)
736        {
737          logger.traceException(e);
738
739          LocalizableMessage message = ERR_ROOTDSE_UNEXPECTED_SEARCH_FAILURE.
740              get(searchOperation.getConnectionID(),
741                  searchOperation.getOperationID(),
742                  stackTraceToSingleLineString(e));
743          throw new DirectoryException(
744                         DirectoryServer.getServerErrorResultCode(), message,
745                         e);
746        }
747        finally
748        {
749          searchOperation.setBaseDN(rootDSEDN);
750        }
751        break;
752
753      default:
754        LocalizableMessage message = ERR_ROOTDSE_INVALID_SEARCH_SCOPE.
755            get(searchOperation.getConnectionID(),
756                searchOperation.getOperationID(),
757                searchOperation.getScope());
758        throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
759    }
760  }
761
762  /**
763   * Returns the subordinate base DNs of the root DSE.
764   *
765   * @return the subordinate base DNs of the root DSE
766   */
767  @SuppressWarnings({ "unchecked", "rawtypes" })
768  public Map<DN, Backend<?>> getSubordinateBaseDNs()
769  {
770    if (subordinateBaseDNs != null)
771    {
772      return subordinateBaseDNs;
773    }
774    return (Map) DirectoryServer.getPublicNamingContexts();
775  }
776
777  @Override
778  public Set<String> getSupportedControls()
779  {
780    return Collections.emptySet();
781  }
782
783  @Override
784  public Set<String> getSupportedFeatures()
785  {
786    return Collections.emptySet();
787  }
788
789  @Override
790  public boolean supports(BackendOperation backendOperation)
791  {
792    // We will only export the DSE entry itself.
793    return backendOperation.equals(BackendOperation.LDIF_EXPORT);
794  }
795
796  @Override
797  public void exportLDIF(LDIFExportConfig exportConfig)
798         throws DirectoryException
799  {
800    // Create the LDIF writer.
801    LDIFWriter ldifWriter;
802    try
803    {
804      ldifWriter = new LDIFWriter(exportConfig);
805    }
806    catch (Exception e)
807    {
808      logger.traceException(e);
809
810      LocalizableMessage message = ERR_ROOTDSE_UNABLE_TO_CREATE_LDIF_WRITER.get(
811          stackTraceToSingleLineString(e));
812      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
813                                   message);
814    }
815
816
817    // Write the root DSE entry itself to it.  Make sure to close the LDIF
818    // writer when we're done.
819    try
820    {
821      ldifWriter.writeEntry(getRootDSE());
822    }
823    catch (Exception e)
824    {
825      logger.traceException(e);
826
827      LocalizableMessage message =
828          ERR_ROOTDSE_UNABLE_TO_EXPORT_DSE.get(stackTraceToSingleLineString(e));
829      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
830                                   message);
831    }
832    finally
833    {
834      close(ldifWriter);
835    }
836  }
837
838  @Override
839  public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext)
840      throws DirectoryException
841  {
842    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
843        ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID()));
844  }
845
846  @Override
847  public void createBackup(BackupConfig backupConfig) throws DirectoryException
848  {
849    LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
850    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
851  }
852
853  @Override
854  public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
855  {
856    LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
857    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
858  }
859
860  @Override
861  public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
862  {
863    LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get();
864    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
865  }
866
867  @Override
868  public boolean isConfigurationAcceptable(RootDSEBackendCfg config,
869                                           List<LocalizableMessage> unacceptableReasons,
870                                           ServerContext serverContext)
871  {
872    return isConfigurationChangeAcceptable(config, unacceptableReasons);
873  }
874
875  @Override
876  public boolean isConfigurationChangeAcceptable(RootDSEBackendCfg cfg, List<LocalizableMessage> unacceptableReasons)
877  {
878    boolean configIsAcceptable = true;
879
880
881    try
882    {
883      Set<DN> subDNs = cfg.getSubordinateBaseDN();
884      if (subDNs.isEmpty())
885      {
886        // This is fine -- we'll just use the set of user-defined suffixes.
887      }
888      else
889      {
890        for (DN baseDN : subDNs)
891        {
892          Backend<?> backend = DirectoryServer.getBackend(baseDN);
893          if (backend == null)
894          {
895            unacceptableReasons.add(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(baseDN));
896            configIsAcceptable = false;
897          }
898        }
899      }
900    }
901    catch (Exception e)
902    {
903      logger.traceException(e);
904
905      unacceptableReasons.add(WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get(
906          stackTraceToSingleLineString(e)));
907      configIsAcceptable = false;
908    }
909
910
911    return configIsAcceptable;
912  }
913
914  @Override
915  public ConfigChangeResult applyConfigurationChange(RootDSEBackendCfg cfg)
916  {
917    final ConfigChangeResult ccr = new ConfigChangeResult();
918
919
920    // Check to see if we should apply a new set of base DNs.
921    ConcurrentHashMap<DN, Backend<?>> subBases;
922    try
923    {
924      Set<DN> subDNs = cfg.getSubordinateBaseDN();
925      if (subDNs.isEmpty())
926      {
927        // This is fine -- we'll just use the set of user-defined suffixes.
928        subBases = null;
929      }
930      else
931      {
932        subBases = new ConcurrentHashMap<>();
933        for (DN baseDN : subDNs)
934        {
935          Backend<?> backend = DirectoryServer.getBackend(baseDN);
936          if (backend == null)
937          {
938            // This is not fine.  We can't use a suffix that doesn't exist.
939            ccr.addMessage(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(baseDN));
940            ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
941          }
942          else
943          {
944            subBases.put(baseDN, backend);
945          }
946        }
947      }
948    }
949    catch (Exception e)
950    {
951      logger.traceException(e);
952
953      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
954      ccr.addMessage(WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get(
955              stackTraceToSingleLineString(e)));
956
957      subBases = null;
958    }
959
960
961    boolean newShowAll = cfg.isShowAllAttributes();
962
963
964    // Check to see if there is a new set of user-defined attributes.
965    ArrayList<Attribute> userAttrs = new ArrayList<>();
966    try
967    {
968      ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN);
969      addAllUserDefinedAttrs(userAttrs, configEntry.getEntry());
970    }
971    catch (ConfigException e)
972    {
973      logger.traceException(e);
974
975      ccr.addMessage(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get(
976              configEntryDN, stackTraceToSingleLineString(e)));
977      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
978    }
979
980
981    if (ccr.getResultCode() == ResultCode.SUCCESS)
982    {
983      subordinateBaseDNs = subBases;
984
985      if (subordinateBaseDNs == null)
986      {
987        ccr.addMessage(INFO_ROOTDSE_USING_SUFFIXES_AS_BASE_DNS.get());
988      }
989      else
990      {
991        String basesStr = "{ " + Utils.joinAsString(", ", subordinateBaseDNs.keySet()) + " }";
992        ccr.addMessage(INFO_ROOTDSE_USING_NEW_SUBORDINATE_BASE_DNS.get(basesStr));
993      }
994
995
996      if (showAllAttributes != newShowAll)
997      {
998        showAllAttributes = newShowAll;
999        ccr.addMessage(INFO_ROOTDSE_UPDATED_SHOW_ALL_ATTRS.get(
1000                ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES, showAllAttributes));
1001      }
1002
1003
1004      userDefinedAttributes = userAttrs;
1005      ccr.addMessage(INFO_ROOTDSE_USING_NEW_USER_ATTRS.get());
1006    }
1007
1008
1009    return ccr;
1010  }
1011}