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