mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
04.45.2007 2249aa0a04b99d513828d8d60c2a8bd7d936b336
Fix issue 1451: constraint and dependency support.

This change adds the server side support for constraints. As part of this change I refactored the ServerManagementContext so that it supports a similar API to the client ManagementContext API. This will make it easier for constraint implementations to perform checks.
3 files added
9 files modified
2688 ■■■■ changed files
opends/src/server/org/opends/server/admin/Constraint.java 17 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java 25 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java 111 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java 84 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java 116 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java 207 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/ServerManagedObject.java 631 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/server/ServerManagementContext.java 727 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java 10 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java 52 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java 519 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java 189 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/Constraint.java
@@ -31,6 +31,7 @@
import java.util.Collection;
import org.opends.server.admin.client.ClientConstraintHandler;
import org.opends.server.admin.server.ServerConstraintHandler;
@@ -68,9 +69,23 @@
   * @return Returns the client-side constraint handlers which will be
   *         used to enforce this constraint in client applications.
   *         The returned collection must not be <code>null</code>
   *         but maybe empty (indicating that the constrain can only
   *         but maybe empty (indicating that the constraint can only
   *         be enforced on the server-side).
   */
  Collection<ClientConstraintHandler> getClientConstraintHandlers();
  /**
   * Gets the server-side constraint handlers which will be used to
   * enforce this constraint within the server.
   *
   * @return Returns the server-side constraint handlers which will be
   *         used to enforce this constraint within the server. The
   *         returned collection must not be <code>null</code> and
   *         must not be empty, since constraints must always be
   *         enforced on the server.
   */
  Collection<ServerConstraintHandler> getServerConstraintHandlers();
}
opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
@@ -25,17 +25,16 @@
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import org.opends.messages.Message;
import java.util.List;
import org.opends.server.admin.DecodingException;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
/**
 * Common features of config listener adaptors.
 */
@@ -51,22 +50,6 @@
  /**
   * Convert a decoding exception to an unacceptable reason.
   *
   * @param e
   *          The decoding exception.
   * @param unacceptableReason
   *          The builder to which messages should be appended.
   */
  protected final void generateUnacceptableReason(
      DecodingException e, MessageBuilder unacceptableReason) {
    // FIXME: generate a property OpenDS style message.
    unacceptableReason.append(e.getLocalizedMessage());
  }
  /**
   * Concatenate a list of messages into a single message.
   *
   * @param reasons
@@ -74,8 +57,8 @@
   * @param unacceptableReason
   *          The single message to which messages should be appended.
   */
  protected final void generateUnacceptableReason(
      List<Message> reasons, MessageBuilder unacceptableReason) {
  protected final void generateUnacceptableReason(List<Message> reasons,
      MessageBuilder unacceptableReason) {
    boolean isFirst = true;
    for (Message reason : reasons) {
      if (isFirst) {
opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
@@ -25,26 +25,34 @@
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import org.opends.messages.Message;
import static org.opends.messages.AdminMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import java.util.LinkedList;
import java.util.List;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.DecodingException;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.OptionalRelationDefinition;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.ResultCode;
import org.opends.messages.MessageBuilder;
/**
@@ -58,20 +66,28 @@
final class ConfigAddListenerAdaptor<S extends Configuration> extends
    AbstractConfigListenerAdaptor implements ConfigAddListener {
  // The managed object path of the parent.
  private final ManagedObjectPath<?, ?> path;
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // Cached configuration object between accept/apply callbacks.
  private S cachedConfiguration;
  // Cached managed object between accept/apply callbacks.
  private ServerManagedObject<? extends S> cachedManagedObject;
  // The instantiable relation.
  private final InstantiableRelationDefinition<?, S> instantiableRelation;
  // The optional relation.
  private final OptionalRelationDefinition<?, S> optionalRelation;
  // The underlying add listener.
  private final ConfigurationAddListener<S> listener;
  // Cached configuration object between accept/apply callbacks.
  private S cachedConfiguration;
  // The optional relation.
  private final OptionalRelationDefinition<?, S> optionalRelation;
  // The managed object path of the parent.
  private final ManagedObjectPath<?, ?> path;
@@ -94,6 +110,7 @@
    this.optionalRelation = null;
    this.listener = listener;
    this.cachedConfiguration = null;
    this.cachedManagedObject = null;
  }
@@ -117,6 +134,7 @@
    this.instantiableRelation = null;
    this.listener = listener;
    this.cachedConfiguration = null;
    this.cachedManagedObject = null;
  }
@@ -124,8 +142,7 @@
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(
      ConfigEntry configEntry) {
  public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry) {
    if (optionalRelation != null) {
      // Optional managed objects are located directly beneath the
      // parent and have a well-defined name. We need to make sure
@@ -140,7 +157,28 @@
    // Cached objects are guaranteed to be from previous acceptable
    // callback.
    return listener.applyConfigurationAdd(cachedConfiguration);
    ConfigChangeResult result = listener
        .applyConfigurationAdd(cachedConfiguration);
    // Now apply post constraint call-backs.
    if (result.getResultCode() == ResultCode.SUCCESS) {
      ManagedObjectDefinition<?, ?> d = cachedManagedObject
          .getManagedObjectDefinition();
      for (Constraint constraint : d.getAllConstraints()) {
        for (ServerConstraintHandler handler : constraint
            .getServerConstraintHandlers()) {
          try {
            handler.performAddPostCondition(cachedManagedObject);
          } catch (ConfigException e) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
    }
    return result;
  }
@@ -154,11 +192,9 @@
    AttributeValue av = dn.getRDN().getAttributeValue(0);
    String name = av.getStringValue().trim();
    ManagedObjectPath<?, ?> childPath;
    RelationDefinition<?, S> r;
    ManagedObjectPath<?, S> childPath;
    if (instantiableRelation != null) {
      childPath = path.child(instantiableRelation, name);
      r = instantiableRelation;
    } else {
      // Optional managed objects are located directly beneath the
      // parent and have a well-defined name. We need to make sure
@@ -169,21 +205,46 @@
        // Doesn't apply to us.
        return true;
      }
      r = optionalRelation;
    }
    ServerManagedObject<? extends S> mo;
    try {
      mo = ServerManagedObject.decode(childPath, r
          .getChildDefinition(), configEntry, configEntry);
      ServerManagementContext context = ServerManagementContext.getInstance();
      cachedManagedObject = context.decode(childPath, configEntry, configEntry);
    } catch (DecodingException e) {
      generateUnacceptableReason(e, unacceptableReason);
      unacceptableReason.append(e.getMessageObject());
      return false;
    }
    cachedConfiguration = mo.getConfiguration();
    cachedConfiguration = cachedManagedObject.getConfiguration();
    List<Message> reasons = new LinkedList<Message>();
    // Enforce any constraints.
    boolean isAcceptable = true;
    ManagedObjectDefinition<?, ?> d = cachedManagedObject
        .getManagedObjectDefinition();
    for (Constraint constraint : d.getAllConstraints()) {
      for (ServerConstraintHandler handler : constraint
          .getServerConstraintHandlers()) {
        try {
          if (!handler.isAddAcceptable(cachedManagedObject, reasons)) {
            isAcceptable = false;
          }
        } catch (ConfigException e) {
          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
              .getMessageObject());
          reasons.add(message);
          isAcceptable = false;
        }
      }
    }
    // Give up immediately if a constraint violation occurs.
    if (!isAcceptable) {
      generateUnacceptableReason(reasons, unacceptableReason);
      return false;
    }
    // Let the add listener decide.
    if (listener.isConfigurationAddAcceptable(cachedConfiguration, reasons)) {
      return true;
    } else {
@@ -195,9 +256,9 @@
  /**
   * Get the configuiration add listener associated with this adaptor.
   * Get the configuration add listener associated with this adaptor.
   *
   * @return Returns the configuiration add listener associated with
   * @return Returns the configuration add listener associated with
   *         this adaptor.
   */
  ConfigurationAddListener<S> getConfigurationAddListener() {
opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
@@ -25,11 +25,14 @@
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import org.opends.messages.Message;
import static org.opends.messages.AdminMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
@@ -40,10 +43,12 @@
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.AliasDefaultBehaviorProvider;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.DecodingException;
import org.opends.server.admin.DefaultBehaviorProvider;
import org.opends.server.admin.DefaultBehaviorProviderVisitor;
import org.opends.server.admin.DefinedDefaultBehaviorProvider;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.PropertyDefinition;
import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
@@ -60,6 +65,7 @@
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.ResultCode;
import org.opends.server.util.StaticUtils;
@@ -199,9 +205,6 @@
  // Cached managed object between accept/apply call-backs.
  private ServerManagedObject<? extends S> cachedManagedObject;
  // The managed object definition.
  private final AbstractManagedObjectDefinition<?, S> d;
  // The names of entries that this change listener depends on.
  private final Set<DN> dependencies;
@@ -216,7 +219,7 @@
  private final ConfigurationChangeListener<? super S> listener;
  // The managed object path.
  private final ManagedObjectPath<?, ?> path;
  private final ManagedObjectPath<?, S> path;
@@ -225,17 +228,13 @@
   *
   * @param path
   *          The managed object path.
   * @param d
   *          The managed object definition.
   * @param listener
   *          The underlying change listener.
   */
  public ConfigChangeListenerAdaptor(ManagedObjectPath<?, ?> path,
      AbstractManagedObjectDefinition<?, S> d,
  public ConfigChangeListenerAdaptor(ManagedObjectPath<?, S> path,
      ConfigurationChangeListener<? super S> listener) {
    this.path = path;
    this.dn = DNBuilder.create(path);
    this.d = d;
    this.listener = listener;
    this.cachedManagedObject = null;
@@ -245,6 +244,7 @@
    this.dependencies = new HashSet<DN>();
    this.dependencyListener = new DependencyConfigChangeListener(dn, this);
    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
    for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
      Visitor.find(path, pd, dependencies);
    }
@@ -311,8 +311,28 @@
    // listener lists.
    cachedManagedObject.setConfigEntry(configEntry);
    return listener.applyConfigurationChange(cachedManagedObject
        .getConfiguration());
    ConfigChangeResult result = listener
        .applyConfigurationChange(cachedManagedObject.getConfiguration());
    // Now apply post constraint call-backs.
    if (result.getResultCode() == ResultCode.SUCCESS) {
      ManagedObjectDefinition<?, ?> d = cachedManagedObject
          .getManagedObjectDefinition();
      for (Constraint constraint : d.getAllConstraints()) {
        for (ServerConstraintHandler handler : constraint
            .getServerConstraintHandlers()) {
          try {
            handler.performModifyPostCondition(cachedManagedObject);
          } catch (ConfigException e) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
    }
    return result;
  }
@@ -350,14 +370,42 @@
  public boolean configChangeIsAcceptable(ConfigEntry configEntry,
      MessageBuilder unacceptableReason, ConfigEntry newConfigEntry) {
    try {
      cachedManagedObject = ServerManagedObject.decode(path, d, configEntry,
          newConfigEntry);
      ServerManagementContext context = ServerManagementContext.getInstance();
      cachedManagedObject = context.decode(path, configEntry, newConfigEntry);
    } catch (DecodingException e) {
      generateUnacceptableReason(e, unacceptableReason);
      unacceptableReason.append(e.getMessageObject());
      return false;
    }
    List<Message> reasons = new LinkedList<Message>();
    // Enforce any constraints.
    boolean isAcceptable = true;
    ManagedObjectDefinition<?, ?> d = cachedManagedObject
        .getManagedObjectDefinition();
    for (Constraint constraint : d.getAllConstraints()) {
      for (ServerConstraintHandler handler : constraint
          .getServerConstraintHandlers()) {
        try {
          if (!handler.isModifyAcceptable(cachedManagedObject, reasons)) {
            isAcceptable = false;
          }
        } catch (ConfigException e) {
          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
              .getMessageObject());
          reasons.add(message);
          isAcceptable = false;
        }
      }
    }
    // Give up immediately if a constraint violation occurs.
    if (!isAcceptable) {
      generateUnacceptableReason(reasons, unacceptableReason);
      return false;
    }
    // Let the change listener decide.
    if (listener.isConfigurationChangeAcceptable(cachedManagedObject
        .getConfiguration(), reasons)) {
      return true;
@@ -390,8 +438,8 @@
      if (configEntry != null) {
        return configEntry;
      } else {
        Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.
            get(String.valueOf(dn));
        Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST
            .get(String.valueOf(dn));
        ErrorLogger.logError(message);
      }
    } catch (ConfigException e) {
opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
@@ -25,26 +25,34 @@
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import org.opends.messages.Message;
import static org.opends.messages.AdminMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import java.util.LinkedList;
import java.util.List;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.DecodingException;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.OptionalRelationDefinition;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.api.ConfigDeleteListener;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.ResultCode;
import org.opends.messages.MessageBuilder;
/**
@@ -56,24 +64,31 @@
 *          The type of server configuration handled by the delete
 *          listener.
 */
final class ConfigDeleteListenerAdaptor<S extends Configuration>
    extends AbstractConfigListenerAdaptor implements
    ConfigDeleteListener {
final class ConfigDeleteListenerAdaptor<S extends Configuration> extends
    AbstractConfigListenerAdaptor implements ConfigDeleteListener {
  // The managed object path of the parent.
  private final ManagedObjectPath<?, ?> path;
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // Cached configuration object between accept/apply callbacks.
  private S cachedConfiguration;
  // Cached managed object between accept/apply callbacks.
  private ServerManagedObject<? extends S> cachedManagedObject;
  // The instantiable relation.
  private final InstantiableRelationDefinition<?, S> instantiableRelation;
  // The optional relation.
  private final OptionalRelationDefinition<?, S> optionalRelation;
  // The underlying delete listener.
  private final ConfigurationDeleteListener<S> listener;
  // Cached configuration object between accept/apply callbacks.
  private S cachedConfiguration;
  // The optional relation.
  private final OptionalRelationDefinition<?, S> optionalRelation;
  // The managed object path of the parent.
  private final ManagedObjectPath<?, ?> path;
@@ -96,6 +111,7 @@
    this.instantiableRelation = relation;
    this.listener = listener;
    this.cachedConfiguration = null;
    this.cachedManagedObject = null;
  }
@@ -119,6 +135,7 @@
    this.instantiableRelation = null;
    this.listener = listener;
    this.cachedConfiguration = null;
    this.cachedManagedObject = null;
  }
@@ -126,8 +143,7 @@
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(
      ConfigEntry configEntry) {
  public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry) {
    if (optionalRelation != null) {
      // Optional managed objects are located directly beneath the
      // parent and have a well-defined name. We need to make sure
@@ -142,7 +158,28 @@
    // Cached objects are guaranteed to be from previous acceptable
    // callback.
    return listener.applyConfigurationDelete(cachedConfiguration);
    ConfigChangeResult result = listener
        .applyConfigurationDelete(cachedConfiguration);
    // Now apply post constraint call-backs.
    if (result.getResultCode() == ResultCode.SUCCESS) {
      ManagedObjectDefinition<?, ?> d = cachedManagedObject
          .getManagedObjectDefinition();
      for (Constraint constraint : d.getAllConstraints()) {
        for (ServerConstraintHandler handler : constraint
            .getServerConstraintHandlers()) {
          try {
            handler.performDeletePostCondition(cachedManagedObject);
          } catch (ConfigException e) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
      }
    }
    return result;
  }
@@ -156,11 +193,9 @@
    AttributeValue av = dn.getRDN().getAttributeValue(0);
    String name = av.getStringValue().trim();
    ManagedObjectPath<?, ?> childPath;
    RelationDefinition<?, S> r;
    ManagedObjectPath<?, S> childPath;
    if (instantiableRelation != null) {
      childPath = path.child(instantiableRelation, name);
      r = instantiableRelation;
    } else {
      // Optional managed objects are located directly beneath the
      // parent and have a well-defined name. We need to make sure
@@ -171,21 +206,46 @@
        // Doesn't apply to us.
        return true;
      }
      r = optionalRelation;
    }
    ServerManagedObject<? extends S> mo;
    try {
      mo = ServerManagedObject.decode(childPath, r
          .getChildDefinition(), configEntry);
      ServerManagementContext context = ServerManagementContext.getInstance();
      cachedManagedObject = context.decode(childPath, configEntry);
    } catch (DecodingException e) {
      generateUnacceptableReason(e, unacceptableReason);
      unacceptableReason.append(e.getMessageObject());
      return false;
    }
    cachedConfiguration = mo.getConfiguration();
    cachedConfiguration = cachedManagedObject.getConfiguration();
    List<Message> reasons = new LinkedList<Message>();
    // Enforce any constraints.
    boolean isAcceptable = true;
    ManagedObjectDefinition<?, ?> d = cachedManagedObject
        .getManagedObjectDefinition();
    for (Constraint constraint : d.getAllConstraints()) {
      for (ServerConstraintHandler handler : constraint
          .getServerConstraintHandlers()) {
        try {
          if (!handler.isDeleteAcceptable(cachedManagedObject, reasons)) {
            isAcceptable = false;
          }
        } catch (ConfigException e) {
          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
              .getMessageObject());
          reasons.add(message);
          isAcceptable = false;
        }
      }
    }
    // Give up immediately if a constraint violation occurs.
    if (!isAcceptable) {
      generateUnacceptableReason(reasons, unacceptableReason);
      return false;
    }
    // Let the delete listener decide.
    if (listener.isConfigurationDeleteAcceptable(cachedConfiguration,
        reasons)) {
      return true;
@@ -201,8 +261,8 @@
   * Get the configuration delete listener associated with this
   * adaptor.
   *
   * @return Returns the configuration delete listener associated
   *         with this adaptor.
   * @return Returns the configuration delete listener associated with
   *         this adaptor.
   */
  ConfigurationDeleteListener<S> getConfigurationDeleteListener() {
    return listener;
opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java
New file
@@ -0,0 +1,207 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import java.util.Collection;
import org.opends.messages.Message;
import org.opends.server.config.ConfigException;
/**
 * An interface for performing server-side constraint validation.
 * <p>
 * Constraints are evaluated immediately before and after write
 * operations are performed. Server-side constraints are evaluated in
 * two phases: the first phase determines if the proposed add, delete,
 * or modification is acceptable according to the constraint. If one
 * or more constraints fails, the write write operation is refused,
 * and the client will receive an
 * <code>OperationRejectedException</code> exception. The second
 * phase is invoked once the add, delete, or modification request has
 * been allowed and any changes applied. The second phase gives the
 * constraint handler a chance to register listener call-backs if
 * required.
 * <p>
 * A server constraint handler must override at least one of the
 * provided methods.
 *
 * @see org.opends.server.admin.Constraint
 */
public abstract class ServerConstraintHandler {
  /**
   * Creates a new server constraint handler.
   */
  protected ServerConstraintHandler() {
    // No implementation required.
  }
  /**
   * Determines whether or not the newly created managed object which
   * is about to be added to the server configuration satisfies this
   * constraint.
   * <p>
   * If the constraint is not satisfied, the implementation must
   * return <code>false</code> and add a message describing why the
   * constraint was not satisfied.
   * <p>
   * The default implementation is to return <code>true</code>.
   *
   * @param managedObject
   *          The new managed object.
   * @param unacceptableReasons
   *          A list of messages to which error messages should be
   *          added.
   * @return Returns <code>true</code> if this constraint is
   *         satisfied, or <code>false</code> if it is not.
   * @throws ConfigException
   *           If an configuration exception prevented this constraint
   *           from being evaluated.
   */
  public boolean isAddAcceptable(ServerManagedObject<?> managedObject,
      Collection<Message> unacceptableReasons) throws ConfigException {
    return true;
  }
  /**
   * Determines whether or not the existing managed object which is
   * about to be deleted from the server configuration satisfies this
   * constraint.
   * <p>
   * If the constraint is not satisfied, the implementation must
   * return <code>false</code> and add a message describing why the
   * constraint was not satisfied.
   * <p>
   * The default implementation is to return <code>true</code>.
   *
   * @param managedObject
   *          The managed object which is about to be deleted.
   * @param unacceptableReasons
   *          A list of messages to which error messages should be
   *          added.
   * @return Returns <code>true</code> if this constraint is
   *         satisfied, or <code>false</code> if it is not.
   * @throws ConfigException
   *           If an configuration exception prevented this constraint
   *           from being evaluated.
   */
  public boolean isDeleteAcceptable(ServerManagedObject<?> managedObject,
      Collection<Message> unacceptableReasons) throws ConfigException {
    return true;
  }
  /**
   * Determines whether or not the changes to an existing managed
   * object which are about to be committed to the server
   * configuration satisfies this constraint.
   * <p>
   * If the constraint is not satisfied, the implementation must
   * return <code>false</code> and add a message describing why the
   * constraint was not satisfied.
   * <p>
   * The default implementation is to return <code>true</code>.
   *
   * @param managedObject
   *          The modified managed object.
   * @param unacceptableReasons
   *          A list of messages to which error messages should be
   *          added.
   * @return Returns <code>true</code> if this modify is satisfied,
   *         or <code>false</code> if it is not.
   * @throws ConfigException
   *           If an configuration exception prevented this constraint
   *           from being evaluated.
   */
  public boolean isModifyAcceptable(ServerManagedObject<?> managedObject,
      Collection<Message> unacceptableReasons) throws ConfigException {
    return true;
  }
  /**
   * Perform any add post-condition processing.
   * <p>
   * The default implementation is to do nothing.
   *
   * @param managedObject
   *          The managed object which was added.
   * @throws ConfigException
   *           If the post-condition processing fails due to a
   *           configuration exception.
   */
  public void performAddPostCondition(ServerManagedObject<?> managedObject)
      throws ConfigException {
    // Do nothing.
  }
  /**
   * Perform any delete post-condition processing.
   * <p>
   * The default implementation is to do nothing.
   *
   * @param managedObject
   *          The managed object which was deleted.
   * @throws ConfigException
   *           If the post-condition processing fails due to a
   *           configuration exception.
   */
  public void performDeletePostCondition(ServerManagedObject<?> managedObject)
      throws ConfigException {
    // Do nothing.
  }
  /**
   * Perform any modify post-condition processing.
   * <p>
   * The default implementation is to do nothing.
   *
   * @param managedObject
   *          The managed object which was modified.
   * @throws ConfigException
   *           If the post-condition processing fails due to a
   *           configuration exception.
   */
  public void performModifyPostCondition(ServerManagedObject<?> managedObject)
      throws ConfigException {
    // Do nothing.
  }
}
opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
@@ -26,55 +26,28 @@
 */
package org.opends.server.admin.server;
import org.opends.messages.Message;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.AliasDefaultBehaviorProvider;
import org.opends.messages.AdminMessages;
import org.opends.messages.Message;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.DefaultBehaviorException;
import org.opends.server.admin.DefaultBehaviorProviderVisitor;
import org.opends.server.admin.DefinedDefaultBehaviorProvider;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.DefinitionResolver;
import org.opends.server.admin.IllegalPropertyValueException;
import org.opends.server.admin.IllegalPropertyValueStringException;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.OptionalRelationDefinition;
import org.opends.server.admin.PropertyDefinition;
import org.opends.server.admin.PropertyException;
import org.opends.server.admin.PropertyIsMandatoryException;
import org.opends.server.admin.PropertyIsSingleValuedException;
import org.opends.server.admin.PropertyNotFoundException;
import org.opends.server.admin.PropertyOption;
import org.opends.server.admin.PropertyProvider;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
import org.opends.server.admin.SingletonRelationDefinition;
import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
import org.opends.server.admin.DefinitionDecodingException.Reason;
import org.opends.server.admin.std.meta.RootCfgDefn;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.AttributeValueDecoder;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.api.ConfigChangeListener;
import org.opends.server.api.ConfigDeleteListener;
@@ -82,12 +55,8 @@
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.messages.AdminMessages;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
@@ -102,518 +71,44 @@
    PropertyProvider {
  /**
   * A default behavior visitor used for retrieving the default values
   * of a property.
   *
   * @param <T>
   *          The type of the property.
   */
  private static class DefaultValueFinder<T> implements
      DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
    /**
     * Get the default values for the specified property.
     *
     * @param <T>
     *          The type of the property.
     * @param p
     *          The managed object path of the current managed object.
     * @param pd
     *          The property definition.
     * @param newConfigEntry
     *          Optional new configuration entry which does not yet
     *          exist in the configuration back-end.
     * @return Returns the default values for the specified property.
     * @throws DefaultBehaviorException
     *           If the default values could not be retrieved or
     *           decoded properly.
     */
    public static <T> Collection<T> getDefaultValues(ManagedObjectPath<?, ?> p,
        PropertyDefinition<T> pd, ConfigEntry newConfigEntry)
        throws DefaultBehaviorException {
      DefaultValueFinder<T> v = new DefaultValueFinder<T>(newConfigEntry);
      return v.find(p, pd);
    }
    // Any exception that occurred whilst retrieving inherited default
    // values.
    private DefaultBehaviorException exception = null;
    // The path of the managed object containing the next property.
    private ManagedObjectPath<?, ?> nextPath = null;
    // The next property whose default values were required.
    private PropertyDefinition<T> nextProperty = null;
    // Optional new configuration entry which does not yet exist in
    // the configuration back-end.
    private ConfigEntry newConfigEntry;
    // Private constructor.
    private DefaultValueFinder(ConfigEntry newConfigEntry) {
      this.newConfigEntry = newConfigEntry;
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitAbsoluteInherited(
        AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
      try {
        return getInheritedProperty(d.getManagedObjectPath(), d
            .getManagedObjectDefinition(), d.getPropertyName());
      } catch (DefaultBehaviorException e) {
        exception = e;
        return Collections.emptySet();
      }
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
      return Collections.emptySet();
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
        Void p) {
      Collection<String> stringValues = d.getDefaultValues();
      List<T> values = new ArrayList<T>(stringValues.size());
      for (String stringValue : stringValues) {
        try {
          values.add(nextProperty.decodeValue(stringValue));
        } catch (IllegalPropertyValueStringException e) {
          exception = new DefaultBehaviorException(nextProperty, e);
          break;
        }
      }
      return values;
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitRelativeInherited(
        RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
      try {
        return getInheritedProperty(d.getManagedObjectPath(nextPath), d
            .getManagedObjectDefinition(), d.getPropertyName());
      } catch (DefaultBehaviorException e) {
        exception = e;
        return Collections.emptySet();
      }
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
        Void p) {
      return Collections.emptySet();
    }
    // Find the default values for the next path/property.
    private Collection<T> find(ManagedObjectPath<?, ?> p,
        PropertyDefinition<T> pd) throws DefaultBehaviorException {
      nextPath = p;
      nextProperty = pd;
      Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(
          this, null);
      if (exception != null) {
        throw exception;
      }
      if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
        throw new DefaultBehaviorException(pd,
            new PropertyIsSingleValuedException(pd));
      }
      return values;
    }
    // Get an inherited property value.
    @SuppressWarnings("unchecked")
    private Collection<T> getInheritedProperty(ManagedObjectPath target,
        AbstractManagedObjectDefinition<?, ?> d, String propertyName)
        throws DefaultBehaviorException {
      // First check that the requested type of managed object
      // corresponds to the path.
      AbstractManagedObjectDefinition<?, ?> supr = target
          .getManagedObjectDefinition();
      if (!supr.isParentOf(d)) {
        throw new DefaultBehaviorException(
            nextProperty, new DefinitionDecodingException(supr,
                Reason.WRONG_TYPE_INFORMATION));
      }
      // Save the current property in case of recursion.
      PropertyDefinition<T> pd1 = nextProperty;
      try {
        // Get the actual managed object definition.
        DN dn = DNBuilder.create(target);
        ConfigEntry configEntry;
        if (newConfigEntry != null && newConfigEntry.getDN().equals(dn)) {
          configEntry = newConfigEntry;
        } else {
          configEntry = getManagedObjectConfigEntry(dn);
        }
        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
        ManagedObjectDefinition<?, ?> mod = d
            .resolveManagedObjectDefinition(resolver);
        PropertyDefinition<T> pd2;
        try {
          PropertyDefinition<?> pdTmp = mod.getPropertyDefinition(propertyName);
          pd2 = pd1.getClass().cast(pdTmp);
        } catch (IllegalArgumentException e) {
          throw new PropertyNotFoundException(propertyName);
        } catch (ClassCastException e) {
          // FIXME: would be nice to throw a better exception here.
          throw new PropertyNotFoundException(propertyName);
        }
        List<String> stringValues = getAttribute(mod, pd2, configEntry);
        if (stringValues.isEmpty()) {
          // Recursively retrieve this property's default values.
          Collection<T> tmp = find(target, pd2);
          Collection<T> values = new ArrayList<T>(tmp.size());
          for (T value : tmp) {
            pd1.validateValue(value);
            values.add(value);
          }
          return values;
        } else {
          Collection<T> values = new ArrayList<T>(stringValues.size());
          for (String s : stringValues) {
            values.add(pd1.decodeValue(s));
          }
          return values;
        }
      } catch (DefinitionDecodingException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (PropertyNotFoundException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (IllegalPropertyValueException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (IllegalPropertyValueStringException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (ConfigException e) {
        throw new DefaultBehaviorException(pd1, e);
      }
    }
  }
  /**
   * A definition resolver that determines the managed object
   * definition from the object classes of a ConfigEntry.
   */
  private static class MyDefinitionResolver implements DefinitionResolver {
    // The config entry.
    private final ConfigEntry entry;
    // Private constructor.
    private MyDefinitionResolver(ConfigEntry entry) {
      this.entry = entry;
    }
    /**
     * {@inheritDoc}
     */
    public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
      String oc = LDAPProfile.getInstance().getObjectClass(d);
      return entry.hasObjectClass(oc);
    }
  };
  /**
   * The root server managed object.
   */
  private static final ServerManagedObject<RootCfg> ROOT =
    new ServerManagedObject<RootCfg>(
      ManagedObjectPath.emptyPath(), RootCfgDefn.getInstance(), Collections
          .<PropertyDefinition<?>, SortedSet<?>> emptyMap(), null);
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Decodes a configuration entry into the required type of server
   * managed object.
   *
   * @param <S>
   *          The type of server configuration represented by the
   *          decoded server managed object.
   * @param path
   *          The location of the server managed object.
   * @param definition
   *          The required managed object type.
   * @param configEntry
   *          The configuration entry that should be decoded.
   * @return Returns the new server-side managed object from the
   *         provided definition and configuration entry.
   * @throws DefinitionDecodingException
   *           If the managed object's type could not be determined.
   * @throws ServerManagedObjectDecodingException
   *           If one or more of the managed object's properties could
   *           not be decoded.
   */
  static <S extends Configuration> ServerManagedObject<? extends S> decode(
      ManagedObjectPath<?, ?> path,
      AbstractManagedObjectDefinition<?, S> definition,
      ConfigEntry configEntry) throws DefinitionDecodingException,
      ServerManagedObjectDecodingException {
    return decode(path, definition, configEntry, null);
  }
  /**
   * Decodes a configuration entry into the required type of server
   * managed object.
   *
   * @param <S>
   *          The type of server configuration represented by the
   *          decoded server managed object.
   * @param path
   *          The location of the server managed object.
   * @param definition
   *          The required managed object type.
   * @param configEntry
   *          The configuration entry that should be decoded.
   * @param newConfigEntry
   *          Optional new configuration that does not exist yet in
   *          the configuration back-end. This will be used for
   *          resolving inherited default values.
   * @return Returns the new server-side managed object from the
   *         provided definition and configuration entry.
   * @throws DefinitionDecodingException
   *           If the managed object's type could not be determined.
   * @throws ServerManagedObjectDecodingException
   *           If one or more of the managed object's properties could
   *           not be decoded.
   */
  static <S extends Configuration> ServerManagedObject<? extends S> decode(
      ManagedObjectPath<?, ?> path,
      AbstractManagedObjectDefinition<?, S> definition,
      ConfigEntry configEntry, ConfigEntry newConfigEntry)
      throws DefinitionDecodingException, ServerManagedObjectDecodingException {
    // First determine the correct definition to use for the entry.
    // This could either be the provided definition, or one of its
    // sub-definitions.
    DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
    ManagedObjectDefinition<?, ? extends S> mod = definition
        .resolveManagedObjectDefinition(resolver);
    // Build the managed object's properties.
    List<PropertyException> exceptions = new LinkedList<PropertyException>();
    Map<PropertyDefinition<?>, SortedSet<?>> properties =
      new HashMap<PropertyDefinition<?>, SortedSet<?>>();
    for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
      List<String> values = getAttribute(mod, pd, configEntry);
      try {
        decodeProperty(properties, path, pd, values, newConfigEntry);
      } catch (PropertyException e) {
        exceptions.add(e);
      }
    }
    // If there were no decoding problems then return the managed
    // object, otherwise throw an operations exception.
    ServerManagedObject<? extends S> mo = decodeAux(path, mod, properties,
        configEntry);
    if (exceptions.isEmpty()) {
      return mo;
    } else {
      throw new ServerManagedObjectDecodingException(mo, exceptions);
    }
  }
  /**
   * Gets the root server managed object.
   *
   * @return Returns the root server managed object.
   */
  static ServerManagedObject<RootCfg> getRootManagedObject() {
    return ROOT;
  }
  // Decode helper method required to avoid generics warning.
  private static <S extends Configuration> ServerManagedObject<S> decodeAux(
      ManagedObjectPath<?, ?> path, ManagedObjectDefinition<?, S> d,
      Map<PropertyDefinition<?>, SortedSet<?>> properties,
      ConfigEntry configEntry) {
    return new ServerManagedObject<S>(path, d, properties, configEntry);
  }
  // Create a property using the provided string values.
  private static <T> void decodeProperty(
      Map<PropertyDefinition<?>, SortedSet<?>> properties,
      ManagedObjectPath<?, ?> path, PropertyDefinition<T> pd,
      List<String> stringValues, ConfigEntry newConfigEntry)
      throws PropertyException {
    PropertyException exception = null;
    SortedSet<T> values = new TreeSet<T>(pd);
    if (!stringValues.isEmpty()) {
      // The property has values defined for it.
      for (String value : stringValues) {
        try {
          values.add(pd.decodeValue(value));
        } catch (IllegalPropertyValueStringException e) {
          exception = e;
        }
      }
    } else {
      // No values defined so get the defaults.
      try {
        values.addAll(DefaultValueFinder.getDefaultValues(path, pd,
            newConfigEntry));
      } catch (DefaultBehaviorException e) {
        exception = e;
      }
    }
    if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
      // This exception takes precedence over previous exceptions.
      exception = new PropertyIsSingleValuedException(pd);
      T value = values.first();
      values.clear();
      values.add(value);
    }
    if (values.isEmpty() && pd.hasOption(PropertyOption.MANDATORY)) {
      // The values maybe empty because of a previous exception.
      if (exception == null) {
        exception = new PropertyIsMandatoryException(pd);
      }
    }
    // TODO: If an exception occurs should we leave the property
    // empty?
    properties.put(pd, values);
    if (exception != null) {
      throw exception;
    }
  }
  // Gets the attribute associated with a property from a ConfigEntry.
  private static List<String> getAttribute(ManagedObjectDefinition<?, ?> d,
      PropertyDefinition<?> pd, ConfigEntry configEntry) {
    // TODO: we create a default attribute type if it is
    // undefined. We should log a warning here if this is the case
    // since the attribute should have been defined.
    String attrID = LDAPProfile.getInstance().getAttributeName(d, pd);
    AttributeType type = DirectoryServer.getAttributeType(attrID, true);
    AttributeValueDecoder<String> decoder =
      new AttributeValueDecoder<String>() {
      public String decode(AttributeValue value) throws DirectoryException {
        return value.getStringValue();
      }
    };
    List<String> values = new LinkedList<String>();
    try {
      configEntry.getEntry().getAttributeValues(type, decoder, values);
    } catch (DirectoryException e) {
      // Should not happen.
      throw new RuntimeException(e);
    }
    return values;
  }
  // Gets a config entry required for a managed object and throws a
  // config exception on failure.
  private static ConfigEntry getManagedObjectConfigEntry(DN dn)
      throws ConfigException {
    ConfigEntry configEntry;
    try {
      configEntry = DirectoryServer.getConfigEntry(dn);
    } catch (ConfigException e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = AdminMessages.ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(
          String.valueOf(dn), stackTraceToSingleLineString(e));
      throw new ConfigException(message, e);
    }
    // The configuration handler is free to return null indicating
    // that the entry does not exist.
    if (configEntry == null) {
      Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.
          get(String.valueOf(dn));
      throw new ConfigException(message);
    }
    return configEntry;
  }
  // The configuration entry associated with this server managed
  // object (null if root).
  private ConfigEntry configEntry;
  // The management context.
  private final ServerManagementContext context = ServerManagementContext
      .getInstance();
  // The managed object's definition.
  private final ManagedObjectDefinition<?, S> definition;
  // The managed object path identifying this managed object's
  // location.
  private final ManagedObjectPath<?, ?> path;
  private final ManagedObjectPath<?, S> path;
  // The managed object's properties.
  private final Map<PropertyDefinition<?>, SortedSet<?>> properties;
  // Create an new server side managed object.
  private ServerManagedObject(ManagedObjectPath<?, ?> path,
  /**
   * Creates an new server side managed object.
   *
   * @param path
   *          The managed object path.
   * @param d
   *          The managed object definition.
   * @param properties
   *          The managed object's properties.
   * @param configEntry
   *          The configuration entry associated with the managed
   *          object.
   */
  ServerManagedObject(ManagedObjectPath<?, S> path,
      ManagedObjectDefinition<?, S> d,
      Map<PropertyDefinition<?>, SortedSet<?>> properties,
      ConfigEntry configEntry) {
@@ -764,8 +259,7 @@
      InstantiableRelationDefinition<?, M> d, String name)
      throws IllegalArgumentException, ConfigException {
    validateRelationDefinition(d);
    ManagedObjectPath<?, ?> childPath = path.child(d, name);
    return getChild(childPath, d);
    return context.getManagedObject(path.child(d, name));
  }
@@ -790,8 +284,7 @@
      OptionalRelationDefinition<?, M> d) throws IllegalArgumentException,
      ConfigException {
    validateRelationDefinition(d);
    ManagedObjectPath<?, ?> childPath = path.child(d);
    return getChild(childPath, d);
    return context.getManagedObject(path.child(d));
  }
@@ -816,8 +309,7 @@
      SingletonRelationDefinition<?, M> d) throws IllegalArgumentException,
      ConfigException {
    validateRelationDefinition(d);
    ManagedObjectPath<?, ?> childPath = path.child(d);
    return getChild(childPath, d);
    return context.getManagedObject(path.child(d));
  }
@@ -869,7 +361,7 @@
   *
   * @return Returns the path of this server managed object.
   */
  public ManagedObjectPath<?, ?> getManagedObjectPath() {
  public ManagedObjectPath<?, S> getManagedObjectPath() {
    return path;
  }
@@ -949,15 +441,7 @@
  public boolean hasChild(OptionalRelationDefinition<?, ?> d)
      throws IllegalArgumentException {
    validateRelationDefinition(d);
    // Get the configuration entry.
    DN targetDN = DNBuilder.create(path, d);
    try {
      return (getManagedObjectConfigEntry(targetDN) != null);
    } catch (ConfigException e) {
      // Assume it doesn't exist.
      return false;
    }
    return context.managedObjectExists(path.child(d));
  }
@@ -976,30 +460,7 @@
  public String[] listChildren(InstantiableRelationDefinition<?, ?> d)
      throws IllegalArgumentException {
    validateRelationDefinition(d);
    // Get the target entry.
    DN targetDN = DNBuilder.create(path, d);
    ConfigEntry configEntry;
    try {
      configEntry = DirectoryServer.getConfigEntry(targetDN);
    } catch (ConfigException e) {
      return new String[0];
    }
    if (configEntry == null) {
      return new String[0];
    }
    // Retrieve the children.
    Set<DN> children = configEntry.getChildren().keySet();
    ArrayList<String> names = new ArrayList<String>(children.size());
    for (DN child : children) {
      // Assume that RDNs are single-valued and can be trimmed.
      AttributeValue av = child.getRDN().getAttributeValue(0);
      names.add(av.getStringValue().trim());
    }
    return names.toArray(new String[names.size()]);
    return context.listManagedObjects(path, d);
  }
@@ -1048,8 +509,8 @@
   *           If the optional relation definition is not associated
   *           with this managed object's definition.
   * @throws ConfigException
   *           If the configuration entry associated with the
   *           optional relation could not be retrieved.
   *           If the configuration entry associated with the optional
   *           relation could not be retrieved.
   */
  public <M extends Configuration> void registerAddListener(
      OptionalRelationDefinition<?, M> d, ConfigurationAddListener<M> listener)
@@ -1073,7 +534,7 @@
  public void registerChangeListener(
      ConfigurationChangeListener<? super S> listener) {
    ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<S>(path,
        definition, listener);
        listener);
    configEntry.registerChangeListener(adaptor);
  }
@@ -1123,8 +584,8 @@
   *           If the optional relation definition is not associated
   *           with this managed object's definition.
   * @throws ConfigException
   *           If the configuration entry associated with the
   *           optional relation could not be retrieved.
   *           If the configuration entry associated with the optional
   *           relation could not be retrieved.
   */
  public <M extends Configuration> void registerDeleteListener(
      OptionalRelationDefinition<?, M> d,
@@ -1206,26 +667,6 @@
  // Get a child managed object.
  private <M extends Configuration> ServerManagedObject<? extends M> getChild(
      ManagedObjectPath<?, ?> childPath, RelationDefinition<?, M> d)
      throws ConfigException {
    // Get the configuration entry.
    DN targetDN = DNBuilder.create(childPath);
    ConfigEntry configEntry = getManagedObjectConfigEntry(targetDN);
    try {
      return decode(childPath, d.getChildDefinition(), configEntry);
    } catch (DefinitionDecodingException e) {
      throw ConfigExceptionFactory.getInstance()
          .createDecodingExceptionAdaptor(targetDN, e);
    } catch (ServerManagedObjectDecodingException e) {
      throw ConfigExceptionFactory.getInstance()
          .createDecodingExceptionAdaptor(e);
    }
  }
  // Gets a config entry required for a listener and throws a config
  // exception on failure or returns null if the entry does not exist.
  private ConfigEntry getListenerConfigEntry(DN dn) throws ConfigException {
@@ -1284,8 +725,8 @@
    }
    // No parent entry could be found.
    Message message = AdminMessages.ERR_ADMIN_UNABLE_TO_REGISTER_LISTENER.get(
        String.valueOf(baseDN));
    Message message = AdminMessages.ERR_ADMIN_UNABLE_TO_REGISTER_LISTENER
        .get(String.valueOf(baseDN));
    throw new ConfigException(message);
  }
@@ -1313,8 +754,8 @@
  // object.
  private void validateRelationDefinition(RelationDefinition<?, ?> rd)
      throws IllegalArgumentException {
    RelationDefinition<?, ?> tmp =
      definition.getRelationDefinition(rd.getName());
    RelationDefinition<?, ?> tmp = definition.getRelationDefinition(rd
        .getName());
    if (tmp != rd) {
      throw new IllegalArgumentException("The relation " + rd.getName()
          + " is not associated with a " + definition.getName());
opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
@@ -29,8 +29,60 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.messages.AdminMessages;
import org.opends.messages.Message;
import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.AliasDefaultBehaviorProvider;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
import org.opends.server.admin.DefaultBehaviorException;
import org.opends.server.admin.DefaultBehaviorProviderVisitor;
import org.opends.server.admin.DefinedDefaultBehaviorProvider;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.DefinitionResolver;
import org.opends.server.admin.IllegalPropertyValueException;
import org.opends.server.admin.IllegalPropertyValueStringException;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.PropertyDefinition;
import org.opends.server.admin.PropertyException;
import org.opends.server.admin.PropertyIsMandatoryException;
import org.opends.server.admin.PropertyIsSingleValuedException;
import org.opends.server.admin.PropertyNotFoundException;
import org.opends.server.admin.PropertyOption;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
import org.opends.server.admin.DefinitionDecodingException.Reason;
import org.opends.server.admin.std.meta.RootCfgDefn;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.AttributeValueDecoder;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
@@ -39,10 +91,255 @@
 */
public final class ServerManagementContext {
  /**
   * A default behavior visitor used for retrieving the default values
   * of a property.
   *
   * @param <T>
   *          The type of the property.
   */
  private class DefaultValueFinder<T> implements
      DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
    // Any exception that occurred whilst retrieving inherited default
    // values.
    private DefaultBehaviorException exception = null;
    // Optional new configuration entry which does not yet exist in
    // the configuration back-end.
    private ConfigEntry newConfigEntry;
    // The path of the managed object containing the next property.
    private ManagedObjectPath<?, ?> nextPath = null;
    // The next property whose default values were required.
    private PropertyDefinition<T> nextProperty = null;
    // Private constructor.
    private DefaultValueFinder(ConfigEntry newConfigEntry) {
      this.newConfigEntry = newConfigEntry;
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitAbsoluteInherited(
        AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
      try {
        return getInheritedProperty(d.getManagedObjectPath(), d
            .getManagedObjectDefinition(), d.getPropertyName());
      } catch (DefaultBehaviorException e) {
        exception = e;
        return Collections.emptySet();
      }
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
      return Collections.emptySet();
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
        Void p) {
      Collection<String> stringValues = d.getDefaultValues();
      List<T> values = new ArrayList<T>(stringValues.size());
      for (String stringValue : stringValues) {
        try {
          values.add(nextProperty.decodeValue(stringValue));
        } catch (IllegalPropertyValueStringException e) {
          exception = new DefaultBehaviorException(nextProperty, e);
          break;
        }
      }
      return values;
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitRelativeInherited(
        RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
      try {
        return getInheritedProperty(d.getManagedObjectPath(nextPath), d
            .getManagedObjectDefinition(), d.getPropertyName());
      } catch (DefaultBehaviorException e) {
        exception = e;
        return Collections.emptySet();
      }
    }
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
        Void p) {
      return Collections.emptySet();
    }
    // Find the default values for the next path/property.
    private Collection<T> find(ManagedObjectPath<?, ?> p,
        PropertyDefinition<T> pd) throws DefaultBehaviorException {
      nextPath = p;
      nextProperty = pd;
      Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(
          this, null);
      if (exception != null) {
        throw exception;
      }
      if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
        throw new DefaultBehaviorException(pd,
            new PropertyIsSingleValuedException(pd));
      }
      return values;
    }
    // Get an inherited property value.
    @SuppressWarnings("unchecked")
    private Collection<T> getInheritedProperty(ManagedObjectPath target,
        AbstractManagedObjectDefinition<?, ?> d, String propertyName)
        throws DefaultBehaviorException {
      // First check that the requested type of managed object
      // corresponds to the path.
      AbstractManagedObjectDefinition<?, ?> supr = target
          .getManagedObjectDefinition();
      if (!supr.isParentOf(d)) {
        throw new DefaultBehaviorException(
            nextProperty, new DefinitionDecodingException(supr,
                Reason.WRONG_TYPE_INFORMATION));
      }
      // Save the current property in case of recursion.
      PropertyDefinition<T> pd1 = nextProperty;
      try {
        // Get the actual managed object definition.
        DN dn = DNBuilder.create(target);
        ConfigEntry configEntry;
        if (newConfigEntry != null && newConfigEntry.getDN().equals(dn)) {
          configEntry = newConfigEntry;
        } else {
          configEntry = getManagedObjectConfigEntry(dn);
        }
        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
        ManagedObjectDefinition<?, ?> mod = d
            .resolveManagedObjectDefinition(resolver);
        PropertyDefinition<T> pd2;
        try {
          PropertyDefinition<?> pdTmp = mod.getPropertyDefinition(propertyName);
          pd2 = pd1.getClass().cast(pdTmp);
        } catch (IllegalArgumentException e) {
          throw new PropertyNotFoundException(propertyName);
        } catch (ClassCastException e) {
          // FIXME: would be nice to throw a better exception here.
          throw new PropertyNotFoundException(propertyName);
        }
        List<String> stringValues = getAttribute(mod, pd2, configEntry);
        if (stringValues.isEmpty()) {
          // Recursively retrieve this property's default values.
          Collection<T> tmp = find(target, pd2);
          Collection<T> values = new ArrayList<T>(tmp.size());
          for (T value : tmp) {
            pd1.validateValue(value);
            values.add(value);
          }
          return values;
        } else {
          Collection<T> values = new ArrayList<T>(stringValues.size());
          for (String s : stringValues) {
            values.add(pd1.decodeValue(s));
          }
          return values;
        }
      } catch (DefinitionDecodingException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (PropertyNotFoundException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (IllegalPropertyValueException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (IllegalPropertyValueStringException e) {
        throw new DefaultBehaviorException(pd1, e);
      } catch (ConfigException e) {
        throw new DefaultBehaviorException(pd1, e);
      }
    }
  }
  /**
   * A definition resolver that determines the managed object
   * definition from the object classes of a ConfigEntry.
   */
  private class MyDefinitionResolver implements DefinitionResolver {
    // The config entry.
    private final ConfigEntry entry;
    // Private constructor.
    private MyDefinitionResolver(ConfigEntry entry) {
      this.entry = entry;
    }
    /**
     * {@inheritDoc}
     */
    public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
      String oc = LDAPProfile.getInstance().getObjectClass(d);
      return entry.hasObjectClass(oc);
    }
  };
  // Singleton instance.
  private final static ServerManagementContext INSTANCE =
    new ServerManagementContext();
  /**
   * The root server managed object.
   */
  private static final ServerManagedObject<RootCfg> ROOT =
    new ServerManagedObject<RootCfg>(
      ManagedObjectPath.emptyPath(), RootCfgDefn.getInstance(), Collections
          .<PropertyDefinition<?>, SortedSet<?>> emptyMap(), null);
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
@@ -64,6 +361,121 @@
  /**
   * Gets the named managed object.
   *
   * @param <C>
   *          The type of client managed object configuration that the
   *          path definition refers to.
   * @param <S>
   *          The type of server managed object configuration that the
   *          path definition refers to.
   * @param path
   *          The path of the managed object.
   * @return Returns the named managed object.
   * @throws ConfigException
   *           If the named managed object could not be found or if it
   *           could not be decoded.
   */
  public <C extends ConfigurationClient, S extends Configuration>
  ServerManagedObject<? extends S> getManagedObject(
      ManagedObjectPath<C, S> path) throws ConfigException {
    // Get the configuration entry.
    DN targetDN = DNBuilder.create(path);
    ConfigEntry configEntry = getManagedObjectConfigEntry(targetDN);
    try {
      return decode(path, configEntry);
    } catch (DefinitionDecodingException e) {
      throw ConfigExceptionFactory.getInstance()
          .createDecodingExceptionAdaptor(targetDN, e);
    } catch (ServerManagedObjectDecodingException e) {
      throw ConfigExceptionFactory.getInstance()
          .createDecodingExceptionAdaptor(e);
    }
  }
  /**
   * Gets the effective value of a property in the named managed
   * object.
   *
   * @param <PD>
   *          The type of the property to be retrieved.
   * @param path
   *          The path of the managed object containing the property.
   * @param pd
   *          The property to be retrieved.
   * @return Returns the property's effective value, or
   *         <code>null</code> if there are no values defined.
   * @throws IllegalArgumentException
   *           If the property definition is not associated with the
   *           referenced managed object's definition.
   * @throws PropertyException
   *           If the managed object was found but the requested
   *           property could not be decoded.
   * @throws ConfigException
   *           If the named managed object could not be found or if it
   *           could not be decoded.
   */
  public <PD> PD getPropertyValue(ManagedObjectPath<?, ?> path,
      PropertyDefinition<PD> pd) throws IllegalArgumentException,
      ConfigException, PropertyException {
    SortedSet<PD> values = getPropertyValues(path, pd);
    if (values.isEmpty()) {
      return null;
    } else {
      return values.first();
    }
  }
  /**
   * Gets the effective values of a property in the named managed
   * object.
   *
   * @param <PD>
   *          The type of the property to be retrieved.
   * @param path
   *          The path of the managed object containing the property.
   * @param pd
   *          The property to be retrieved.
   * @return Returns the property's effective values, or an empty set
   *         if there are no values defined.
   * @throws IllegalArgumentException
   *           If the property definition is not associated with the
   *           referenced managed object's definition.
   * @throws PropertyException
   *           If the managed object was found but the requested
   *           property could not be decoded.
   * @throws ConfigException
   *           If the named managed object could not be found or if it
   *           could not be decoded.
   */
  public <PD> SortedSet<PD> getPropertyValues(ManagedObjectPath<?, ?> path,
      PropertyDefinition<PD> pd) throws IllegalArgumentException,
      ConfigException, PropertyException {
    DN dn = DNBuilder.create(path);
    ConfigEntry configEntry = getManagedObjectConfigEntry(dn);
    DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
    ManagedObjectDefinition<?, ?> mod;
    try {
      mod = d.resolveManagedObjectDefinition(resolver);
    } catch (DefinitionDecodingException e) {
      throw ConfigExceptionFactory.getInstance()
          .createDecodingExceptionAdaptor(dn, e);
    }
    List<String> values = getAttribute(mod, pd, configEntry);
    return decodeProperty(path, pd, values, null);
  }
  /**
   * Get the root configuration manager associated with this
   * management context.
   *
@@ -84,6 +496,319 @@
   *         associated with this management context.
   */
  public ServerManagedObject<RootCfg> getRootConfigurationManagedObject() {
    return ServerManagedObject.getRootManagedObject();
    return ROOT;
  }
  /**
   * Lists the child managed objects of the named parent managed
   * object.
   *
   * @param <C>
   *          The type of client managed object configuration that the
   *          relation definition refers to.
   * @param <S>
   *          The type of server managed object configuration that the
   *          relation definition refers to.
   * @param parent
   *          The path of the parent managed object.
   * @param rd
   *          The instantiable relation definition.
   * @return Returns the names of the child managed objects.
   * @throws IllegalArgumentException
   *           If the relation definition is not associated with the
   *           parent managed object's definition.
   */
  public <C extends ConfigurationClient, S extends Configuration>
  String[] listManagedObjects(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd)
      throws IllegalArgumentException {
    validateRelationDefinition(parent, rd);
    // Get the target entry.
    DN targetDN = DNBuilder.create(parent, rd);
    ConfigEntry configEntry;
    try {
      configEntry = DirectoryServer.getConfigEntry(targetDN);
    } catch (ConfigException e) {
      return new String[0];
    }
    if (configEntry == null) {
      return new String[0];
    }
    // Retrieve the children.
    Set<DN> children = configEntry.getChildren().keySet();
    ArrayList<String> names = new ArrayList<String>(children.size());
    for (DN child : children) {
      // Assume that RDNs are single-valued and can be trimmed.
      AttributeValue av = child.getRDN().getAttributeValue(0);
      names.add(av.getStringValue().trim());
    }
    return names.toArray(new String[names.size()]);
  }
  /**
   * Determines whether or not the named managed object exists.
   *
   * @param path
   *          The path of the named managed object.
   * @return Returns <code>true</code> if the named managed object
   *         exists, <code>false</code> otherwise.
   */
  public boolean managedObjectExists(ManagedObjectPath<?, ?> path) {
    // Get the configuration entry.
    DN targetDN = DNBuilder.create(path);
    try {
      return (getManagedObjectConfigEntry(targetDN) != null);
    } catch (ConfigException e) {
      // Assume it doesn't exist.
      return false;
    }
  }
  /**
   * Decodes a configuration entry into the required type of server
   * managed object.
   *
   * @param <C>
   *          The type of client managed object configuration that the
   *          path definition refers to.
   * @param <S>
   *          The type of server managed object configuration that the
   *          path definition refers to.
   * @param path
   *          The location of the server managed object.
   * @param configEntry
   *          The configuration entry that should be decoded.
   * @return Returns the new server-side managed object from the
   *         provided definition and configuration entry.
   * @throws DefinitionDecodingException
   *           If the managed object's type could not be determined.
   * @throws ServerManagedObjectDecodingException
   *           If one or more of the managed object's properties could
   *           not be decoded.
   */
  <C extends ConfigurationClient, S extends Configuration>
  ServerManagedObject<? extends S> decode(
      ManagedObjectPath<C, S> path, ConfigEntry configEntry)
      throws DefinitionDecodingException, ServerManagedObjectDecodingException {
    return decode(path, configEntry, null);
  }
  /**
   * Decodes a configuration entry into the required type of server
   * managed object.
   *
   * @param <C>
   *          The type of client managed object configuration that the
   *          path definition refers to.
   * @param <S>
   *          The type of server managed object configuration that the
   *          path definition refers to.
   * @param path
   *          The location of the server managed object.
   * @param configEntry
   *          The configuration entry that should be decoded.
   * @param newConfigEntry
   *          Optional new configuration that does not exist yet in
   *          the configuration back-end. This will be used for
   *          resolving inherited default values.
   * @return Returns the new server-side managed object from the
   *         provided definition and configuration entry.
   * @throws DefinitionDecodingException
   *           If the managed object's type could not be determined.
   * @throws ServerManagedObjectDecodingException
   *           If one or more of the managed object's properties could
   *           not be decoded.
   */
  <C extends ConfigurationClient, S extends Configuration>
  ServerManagedObject<? extends S> decode(
      ManagedObjectPath<C, S> path, ConfigEntry configEntry,
      ConfigEntry newConfigEntry) throws DefinitionDecodingException,
      ServerManagedObjectDecodingException {
    // First determine the correct definition to use for the entry.
    // This could either be the provided definition, or one of its
    // sub-definitions.
    DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
    AbstractManagedObjectDefinition<C, S> d = path.getManagedObjectDefinition();
    ManagedObjectDefinition<? extends C, ? extends S> mod = d
        .resolveManagedObjectDefinition(resolver);
    // Build the managed object's properties.
    List<PropertyException> exceptions = new LinkedList<PropertyException>();
    Map<PropertyDefinition<?>, SortedSet<?>> properties =
      new HashMap<PropertyDefinition<?>, SortedSet<?>>();
    for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
      List<String> values = getAttribute(mod, pd, configEntry);
      try {
        SortedSet<?> pvalues = decodeProperty(path, pd, values, newConfigEntry);
        properties.put(pd, pvalues);
      } catch (PropertyException e) {
        exceptions.add(e);
      }
    }
    // If there were no decoding problems then return the managed
    // object, otherwise throw an operations exception.
    ServerManagedObject<? extends S> mo = decodeAux(path, mod, properties,
        configEntry);
    if (exceptions.isEmpty()) {
      return mo;
    } else {
      throw new ServerManagedObjectDecodingException(mo, exceptions);
    }
  }
  // Decode helper method required to avoid generics warning.
  private <C extends ConfigurationClient, S extends Configuration>
  ServerManagedObject<S> decodeAux(
      ManagedObjectPath<? super C, ? super S> path,
      ManagedObjectDefinition<C, S> d,
      Map<PropertyDefinition<?>, SortedSet<?>> properties,
      ConfigEntry configEntry) {
    ManagedObjectPath<C, S> newPath = path.asSubType(d);
    return new ServerManagedObject<S>(newPath, d, properties, configEntry);
  }
  // Create a property using the provided string values.
  private <T> SortedSet<T> decodeProperty(ManagedObjectPath<?, ?> path,
      PropertyDefinition<T> pd, List<String> stringValues,
      ConfigEntry newConfigEntry) throws PropertyException {
    PropertyException exception = null;
    SortedSet<T> values = new TreeSet<T>(pd);
    if (!stringValues.isEmpty()) {
      // The property has values defined for it.
      for (String value : stringValues) {
        try {
          values.add(pd.decodeValue(value));
        } catch (IllegalPropertyValueStringException e) {
          exception = e;
        }
      }
    } else {
      // No values defined so get the defaults.
      try {
        values.addAll(getDefaultValues(path, pd, newConfigEntry));
      } catch (DefaultBehaviorException e) {
        exception = e;
      }
    }
    if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
      // This exception takes precedence over previous exceptions.
      exception = new PropertyIsSingleValuedException(pd);
      T value = values.first();
      values.clear();
      values.add(value);
    }
    if (values.isEmpty() && pd.hasOption(PropertyOption.MANDATORY)) {
      // The values maybe empty because of a previous exception.
      if (exception == null) {
        exception = new PropertyIsMandatoryException(pd);
      }
    }
    if (exception != null) {
      throw exception;
    } else {
      return values;
    }
  }
  // Gets the attribute associated with a property from a ConfigEntry.
  private List<String> getAttribute(ManagedObjectDefinition<?, ?> d,
      PropertyDefinition<?> pd, ConfigEntry configEntry) {
    // TODO: we create a default attribute type if it is
    // undefined. We should log a warning here if this is the case
    // since the attribute should have been defined.
    String attrID = LDAPProfile.getInstance().getAttributeName(d, pd);
    AttributeType type = DirectoryServer.getAttributeType(attrID, true);
    AttributeValueDecoder<String> decoder =
      new AttributeValueDecoder<String>() {
      public String decode(AttributeValue value) throws DirectoryException {
        return value.getStringValue();
      }
    };
    List<String> values = new LinkedList<String>();
    try {
      configEntry.getEntry().getAttributeValues(type, decoder, values);
    } catch (DirectoryException e) {
      // Should not happen.
      throw new RuntimeException(e);
    }
    return values;
  }
  // Get the default values for the specified property.
  private <T> Collection<T> getDefaultValues(ManagedObjectPath<?, ?> p,
      PropertyDefinition<T> pd, ConfigEntry newConfigEntry)
      throws DefaultBehaviorException {
    DefaultValueFinder<T> v = new DefaultValueFinder<T>(newConfigEntry);
    return v.find(p, pd);
  }
  // Gets a config entry required for a managed object and throws a
  // config exception on failure.
  private ConfigEntry getManagedObjectConfigEntry(
      DN dn) throws ConfigException {
    ConfigEntry configEntry;
    try {
      configEntry = DirectoryServer.getConfigEntry(dn);
    } catch (ConfigException e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = AdminMessages.ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(
          String.valueOf(dn), stackTraceToSingleLineString(e));
      throw new ConfigException(message, e);
    }
    // The configuration handler is free to return null indicating
    // that the entry does not exist.
    if (configEntry == null) {
      Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST
          .get(String.valueOf(dn));
      throw new ConfigException(message);
    }
    return configEntry;
  }
  // Validate that a relation definition belongs to the path.
  private void validateRelationDefinition(ManagedObjectPath<?, ?> path,
      RelationDefinition<?, ?> rd) throws IllegalArgumentException {
    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
    RelationDefinition<?, ?> tmp = d.getRelationDefinition(rd.getName());
    if (tmp != rd) {
      throw new IllegalArgumentException("The relation " + rd.getName()
          + " is not associated with a " + d.getName());
    }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java
@@ -39,6 +39,7 @@
import org.opends.server.admin.client.CommunicationException;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.admin.server.ServerConstraintHandler;
@@ -138,4 +139,13 @@
    return Collections.<ClientConstraintHandler> singleton(new Handler());
  }
  /**
   * {@inheritDoc}
   */
  public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
    return Collections.emptySet();
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java
@@ -30,8 +30,13 @@
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.admin.SingletonRelationDefinition;
import org.opends.server.admin.std.meta.RootCfgDefn;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.types.Entry;
@@ -44,6 +49,15 @@
 */
public final class AdminTestCaseUtils {
  // The relation name which will be used for dummy configurations. A
  // deliberately obfuscated name is chosen to avoid clashes.
  private static final String DUMMY_TEST_RELATION = "*dummy*test*relation*";
  // Indicates if the dummy relation profile has been registered.
  private static boolean isProfileRegistered = false;
  // Prevent instantiation.
  private AdminTestCaseUtils() {
    // No implementation required.
@@ -71,8 +85,8 @@
    ConfigEntry configEntry = new ConfigEntry(entry, null);
    try {
      ServerManagedObject<? extends S> mo = ServerManagedObject
          .decode(ManagedObjectPath.emptyPath(), definition,
      ServerManagementContext context = ServerManagementContext.getInstance();
      ServerManagedObject<? extends S> mo = context.decode(getPath(definition),
              configEntry);
      return mo.getConfiguration();
    } catch (DefinitionDecodingException e) {
@@ -83,4 +97,38 @@
          .createDecodingExceptionAdaptor(e);
    }
  }
  // Construct a dummy path.
  private synchronized static <C extends ConfigurationClient, S extends Configuration>
  ManagedObjectPath<C, S> getPath(AbstractManagedObjectDefinition<C, S> d) {
    if (!isProfileRegistered) {
      LDAPProfile.Wrapper profile = new LDAPProfile.Wrapper() {
        /**
         * {@inheritDoc}
         */
        @Override
        public String getRelationRDNSequence(RelationDefinition<?, ?> r) {
          if (r.getName().equals(DUMMY_TEST_RELATION)) {
            return "cn=dummy configuration,cn=config";
          } else {
            return null;
          }
        }
      };
      LDAPProfile.getInstance().pushWrapper(profile);
      isProfileRegistered = true;
    }
    SingletonRelationDefinition.Builder<C, S> builder =
      new SingletonRelationDefinition.Builder<C, S>(
        RootCfgDefn.getInstance(), DUMMY_TEST_RELATION, d);
    ManagedObjectPath<?, ?> root = ManagedObjectPath.emptyPath();
    return root.child(builder.getInstance());
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java
New file
@@ -0,0 +1,519 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import java.util.List;
import javax.naming.OperationNotSupportedException;
import javax.naming.ldap.LdapName;
import org.opends.messages.Message;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.AdminTestCase;
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.MockLDAPProfile;
import org.opends.server.admin.TestCfg;
import org.opends.server.admin.TestChildCfg;
import org.opends.server.admin.TestChildCfgDefn;
import org.opends.server.admin.TestParentCfg;
import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
 * Test cases for constraints on the server-side.
 */
public final class ConstraintTest extends AdminTestCase {
  // Child DN.
  private static final String TEST_CHILD_1_DN =
    "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config";
  /**
   * A test child add listener.
   */
  private static class AddListener implements
      ConfigurationAddListener<TestChildCfg> {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationAdd(TestChildCfg configuration) {
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationAddAcceptable(TestChildCfg configuration,
        List<Message> unacceptableReasons) {
      return true;
    }
  }
  /**
   * A test child delete listener.
   */
  private static class DeleteListener implements
      ConfigurationDeleteListener<TestChildCfg> {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationDelete(
        TestChildCfg configuration) {
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationDeleteAcceptable(TestChildCfg configuration,
        List<Message> unacceptableReasons) {
      return true;
    }
  }
  /**
   * A test child change listener.
   */
  private static class ChangeListener implements
      ConfigurationChangeListener<TestChildCfg> {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationChange(
        TestChildCfg configuration) {
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationChangeAcceptable(TestChildCfg configuration,
        List<Message> unacceptableReasons) {
      return true;
    }
  }
  // Test child 1 LDIF.
  private static final String[] TEST_CHILD_1 = new String[] {
      "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-virtual-attribute",
      "cn: test child 1",
      "ds-cfg-virtual-attribute-enabled: true",
      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-virtual-attribute-type: description",
      "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real"
  };
  // Test LDIF.
  private static final String[] TEST_LDIF = new String[] {
      // Base entries.
      "dn: cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test parents",
      "",
      // Parent 1 - uses default values for
      // optional-multi-valued-dn-property.
      "dn: cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-virtual-attribute",
      "cn: test parent 1",
      "ds-cfg-virtual-attribute-enabled: true",
      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-virtual-attribute-type: description",
      "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real",
      "",
      // Child base entries.
      "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test children",
      "",
  };
  // JNDI LDAP context.
  private JNDIDirContextAdaptor adaptor = null;
  /**
   * Sets up tests
   *
   * @throws Exception
   *           If the server could not be initialized.
   */
  @BeforeClass
  public void setUp() throws Exception {
    // This test suite depends on having the schema available, so
    // we'll start the server.
    TestCaseUtils.startServer();
    LDAPProfile.getInstance().pushWrapper(new MockLDAPProfile());
    // Add test managed objects.
    TestCaseUtils.addEntries(TEST_LDIF);
  }
  /**
   * Tears down test environment.
   *
   * @throws Exception
   *           If the test entries could not be removed.
   */
  @AfterClass
  public void tearDown() throws Exception {
    LDAPProfile.getInstance().popWrapper();
    TestCfg.cleanup();
    // Remove test entries.
    deleteSubtree("cn=test parents,cn=config");
  }
  /**
   * Tests that an add constraint can succeed.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAddConstraintSuccess() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    AddListener listener = new AddListener();
    parent.addTestChildAddListener(listener);
    MockConstraint constraint = new MockConstraint(true, false, false);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      try {
        // Add the entry.
        addEntry(ResultCode.SUCCESS, TEST_CHILD_1);
      } finally {
        try {
          deleteSubtree(TEST_CHILD_1_DN);
        } catch (Exception e) {
          // Do nothing.
        }
      }
    } finally {
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
      parent.removeTestChildAddListener(listener);
    }
  }
  /**
   * Tests that an add constraint can fail.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAddConstraintFail() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    AddListener listener = new AddListener();
    parent.addTestChildAddListener(listener);
    MockConstraint constraint = new MockConstraint(false, true, true);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      try {
        // Add the entry.
        addEntry(ResultCode.UNWILLING_TO_PERFORM, TEST_CHILD_1);
      } finally {
        try {
          deleteSubtree(TEST_CHILD_1_DN);
        } catch (Exception e) {
          // Do nothing.
        }
      }
    } finally {
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
      parent.removeTestChildAddListener(listener);
    }
  }
  /**
   * Tests that a delete constraint can succeed.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testDeleteConstraintSuccess() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    DeleteListener listener = new DeleteListener();
    parent.addTestChildDeleteListener(listener);
    MockConstraint constraint = new MockConstraint(false, false, true);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      // Now delete it - this should trigger the constraint.
      deleteSubtree(TEST_CHILD_1_DN);
    } finally {
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
      parent.removeTestChildDeleteListener(listener);
      try {
        // Clean up.
        deleteSubtree(TEST_CHILD_1_DN);
      } catch (Exception e) {
        // Ignore.
      }
    }
  }
  /**
   * Tests that a delete constraint can fail.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testDeleteConstraintFail() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    DeleteListener listener = new DeleteListener();
    parent.addTestChildDeleteListener(listener);
    MockConstraint constraint = new MockConstraint(true, true, false);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      try {
        // Now delete it - this should trigger the constraint.
        deleteSubtree(TEST_CHILD_1_DN);
        // Should not have succeeded.
        Assert.fail("Delete constraint failed to prevent deletion");
      } catch (OperationNotSupportedException e) {
        // Ignore - this is the expected exception.
      }
    } finally {
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
      parent.removeTestChildDeleteListener(listener);
      try {
        // Clean up.
        deleteSubtree(TEST_CHILD_1_DN);
      } catch (Exception e) {
        // Ignore.
      }
    }
  }
  /**
   * Tests that a modify constraint can succeed.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChangeConstraintSuccess() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    MockConstraint constraint = new MockConstraint(false, true, false);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      TestChildCfg child = parent.getTestChild("test child 1");
      ChangeListener listener = new ChangeListener();
      child.addChangeListener(listener);
      // Now modify it.
      String[] changes = new String[] {
          "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "replace: ds-cfg-virtual-attribute-base-dn",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com",
          "-",
          "replace: ds-cfg-virtual-attribute-group-dn",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 3,dc=com",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 4,dc=com"
      };
      int result = TestCaseUtils.applyModifications(changes);
      Assert.assertEquals(result, ResultCode.SUCCESS.getIntValue());
    } finally {
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
      try {
        deleteSubtree(TEST_CHILD_1_DN);
      } catch (Exception e) {
        // Ignore.
      }
    }
  }
  /**
   * Tests that a modify constraint can fail.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChangeConstraintFail() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    MockConstraint constraint = new MockConstraint(true, false, true);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      TestChildCfg child = parent.getTestChild("test child 1");
      ChangeListener listener = new ChangeListener();
      child.addChangeListener(listener);
      // Now modify it.
      String[] changes = new String[] {
          "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "replace: ds-cfg-virtual-attribute-base-dn",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com",
          "-",
          "replace: ds-cfg-virtual-attribute-group-dn",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 3,dc=com",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 4,dc=com"
      };
      int result = TestCaseUtils.applyModifications(changes);
      Assert
          .assertEquals(result, ResultCode.UNWILLING_TO_PERFORM.getIntValue());
    } finally {
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
      try {
        deleteSubtree(TEST_CHILD_1_DN);
      } catch (Exception e) {
        // Ignore.
      }
    }
  }
  // Add an entry and check its result.
  private void addEntry(ResultCode expected, String... lines) throws Exception {
    Entry entry = TestCaseUtils.makeEntry(lines);
    InternalClientConnection conn = InternalClientConnection
        .getRootConnection();
    AddOperation add = conn.processAdd(entry.getDN(), entry.getObjectClasses(),
        entry.getUserAttributes(), entry.getOperationalAttributes());
    Assert.assertEquals(add.getResultCode(), expected, add.getErrorMessage()
        .toString());
  }
  // Deletes the named sub-tree.
  private void deleteSubtree(String dn) throws Exception {
    getAdaptor().deleteSubtree(new LdapName(dn));
  }
  // Gets the JNDI connection for the test server instance.
  private synchronized JNDIDirContextAdaptor getAdaptor() throws Exception {
    if (adaptor == null) {
      adaptor = JNDIDirContextAdaptor.simpleBind("127.0.0.1", TestCaseUtils
          .getServerLdapPort(), "cn=directory manager", "password");
    }
    return adaptor;
  }
  // Gets the named parent configuration.
  private TestParentCfg getParent(String name) throws IllegalArgumentException,
      ConfigException {
    ServerManagementContext ctx = ServerManagementContext.getInstance();
    ServerManagedObject<RootCfg> root = ctx.getRootConfigurationManagedObject();
    TestParentCfg parent = root.getChild(
        TestCfg.getTestOneToManyParentRelationDefinition(), name)
        .getConfiguration();
    return parent;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java
New file
@@ -0,0 +1,189 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import java.util.Collection;
import java.util.Collections;
import org.opends.messages.Message;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.client.ClientConstraintHandler;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DN;
import org.testng.Assert;
/**
 * A mock constraint which can be configured to refuse various types
 * of operation.
 */
public final class MockConstraint implements Constraint {
  /**
   * Mock server constraint handler.
   */
  private class Handler extends ServerConstraintHandler {
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAddAcceptable(ServerManagedObject<?> managedObject,
        Collection<Message> unacceptableReasons) throws ConfigException {
      if (!allowAdds) {
        unacceptableReasons.add(Message.raw("Adds not allowed"));
      }
      return allowAdds;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDeleteAcceptable(ServerManagedObject<?> managedObject,
        Collection<Message> unacceptableReasons) throws ConfigException {
      if (!allowDeletes) {
        unacceptableReasons.add(Message.raw("Deletes not allowed"));
      }
      return allowDeletes;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isModifyAcceptable(ServerManagedObject<?> managedObject,
        Collection<Message> unacceptableReasons) throws ConfigException {
      if (!allowModifies) {
        unacceptableReasons.add(Message.raw("Modifies not allowed"));
      }
      return allowModifies;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void performAddPostCondition(ServerManagedObject<?> managedObject)
        throws ConfigException {
      // Make sure that the associated config entry exists.
      DN targetDN = managedObject.getDN();
      ConfigEntry configEntry = DirectoryServer.getConfigEntry(targetDN);
      Assert.assertNotNull(configEntry);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void performDeletePostCondition(ServerManagedObject<?> managedObject)
        throws ConfigException {
      // Make sure that the associated config entry does not exist.
      DN targetDN = managedObject.getDN();
      ConfigEntry configEntry = DirectoryServer.getConfigEntry(targetDN);
      Assert.assertNull(configEntry);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void performModifyPostCondition(ServerManagedObject<?> managedObject)
        throws ConfigException {
      // Make sure that the associated config entry exists.
      DN targetDN = managedObject.getDN();
      ConfigEntry configEntry = DirectoryServer.getConfigEntry(targetDN);
      Assert.assertNotNull(configEntry);
    }
  }
  // Determines if add operations are allowed.
  private final boolean allowAdds;
  // Determines if modify operations are allowed.
  private final boolean allowModifies;
  // Determines if delete operations are allowed.
  private final boolean allowDeletes;
  /**
   * Creates a new mock constraint.
   *
   * @param allowAdds
   *          Determines if add operations are allowed.
   * @param allowModifies
   *          Determines if modify operations are allowed.
   * @param allowDeletes
   *          Determines if delete operations are allowed.
   */
  public MockConstraint(boolean allowAdds, boolean allowModifies,
      boolean allowDeletes) {
    this.allowAdds = allowAdds;
    this.allowModifies = allowModifies;
    this.allowDeletes = allowDeletes;
  }
  /**
   * {@inheritDoc}
   */
  public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
    return Collections.emptySet();
  }
  /**
   * {@inheritDoc}
   */
  public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
    return Collections.<ServerConstraintHandler> singleton(new Handler());
  }
}