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