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

matthew_swift
03.33.2007 8532a5133e996e61765be126f8b4d25984745fd1
Partial fix for issue 1451: admin framework constraint and dependency support.

This change provides a client-side framework for enforcing arbitrary constraints between managed objects and their properties. This framework enables us to (not exhaustive):

1) support referential integrity between managed objects (required by issue 1449)

2) support dependencies between properties (e.g. if A is true then B is mandatory). This is useful for optional features like SSL configuration

3) support constraints between properties (e.g. integer property A must always be less than integer property B)

A subsequent change will provide server-side support.
3 files added
12 files modified
1563 ■■■■ changed files
opends/src/messages/messages/admin.properties 2 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/AbstractManagedObjectDefinition.java 73 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/Constraint.java 76 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/ClientConstraintHandler.java 156 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/ManagedObject.java 22 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/ManagementContext.java 359 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/OperationRejectedException.java 109 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/ldap/LDAPDriver.java 144 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java 40 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/ldap/LDAPManagementContext.java 10 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/spi/AbstractManagedObject.java 37 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/client/spi/Driver.java 171 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestChildCfgDefn.java 22 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/LDAPClientTest.java 201 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java 141 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/admin.properties
@@ -186,3 +186,5 @@
 administrator's properties
INFO_ADMIN_ARG_USERID_DESCRIPTION_73=The administrator's unique identifier. \
 This is a required argument
SEVERE_ERR_OPERATION_REJECTED_DEFAULT_74=The operation was rejected for an \
 unspecified reason
opends/src/server/org/opends/server/admin/AbstractManagedObjectDefinition.java
@@ -26,7 +26,6 @@
 */
package org.opends.server.admin;
import org.opends.messages.Message;
@@ -35,12 +34,14 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import org.opends.messages.Message;
import org.opends.server.admin.DefinitionDecodingException.Reason;
@@ -68,6 +69,10 @@
  // The parent managed object definition if applicable.
  private final AbstractManagedObjectDefinition<? super C, ? super S> parent;
  // The set of constraints associated with this managed object
  // definition.
  private final Collection<Constraint> constraints;
  // The set of property definitions applicable to this managed object
  // definition.
  private final Map<String, PropertyDefinition<?>> propertyDefinitions;
@@ -76,6 +81,10 @@
  // definition.
  private final Map<String, RelationDefinition<?, ?>> relationDefinitions;
  // The set of all constraints associated with this managed object
  // definition including inherited constraints.
  private final Collection<Constraint> allConstraints;
  // The set of all property definitions associated with this managed
  // object definition including inherited property definitions.
  private final Map<String, PropertyDefinition<?>> allPropertyDefinitions;
@@ -106,8 +115,10 @@
      AbstractManagedObjectDefinition<? super C, ? super S> parent) {
    this.name = name;
    this.parent = parent;
    this.constraints = new LinkedList<Constraint>();
    this.propertyDefinitions = new HashMap<String, PropertyDefinition<?>>();
    this.relationDefinitions = new HashMap<String, RelationDefinition<?,?>>();
    this.allConstraints = new LinkedList<Constraint>();
    this.allPropertyDefinitions = new HashMap<String, PropertyDefinition<?>>();
    this.allRelationDefinitions =
      new HashMap<String, RelationDefinition<?, ?>>();
@@ -119,6 +130,8 @@
    if (parent != null) {
      parent.children.put(name, this);
      allConstraints.addAll(parent.getAllConstraints());
      for (PropertyDefinition<?> pd : parent.getAllPropertyDefinitions()) {
        allPropertyDefinitions.put(pd.getName(), pd);
      }
@@ -158,6 +171,20 @@
  /**
   * Get all the constraints associated with this type of managed
   * object. The returned collection will contain inherited
   * constraints.
   *
   * @return Returns an unmodifiable collection containing all the
   *         constraints associated with this type of managed object.
   */
  public final Collection<Constraint> getAllConstraints() {
    return Collections.unmodifiableCollection(allConstraints);
  }
  /**
   * Get all the property definitions associated with this type of
   * managed object. The returned collection will contain inherited
   * property definitions.
@@ -263,6 +290,19 @@
  /**
   * Get the constraints defined by this managed object definition.
   * The returned collection will not contain inherited constraints.
   *
   * @return Returns an unmodifiable collection containing the
   *         constraints defined by this managed object definition.
   */
  public final Collection<Constraint> getConstraints() {
    return Collections.unmodifiableCollection(constraints);
  }
  /**
   * Gets the optional description of this managed object definition
   * in the default locale.
   *
@@ -635,6 +675,22 @@
  /**
   * Deregister a constraint from the managed object definition.
   * <p>
   * This method <b>must not</b> be called by applications and is
   * only intended for internal testing.
   *
   * @param constraint
   *          The constraint to be deregistered.
   */
  protected final void deregisterConstraint(Constraint constraint) {
    constraints.remove(constraint);
    allConstraints.remove(constraint);
  }
  /**
   * Deregister a relation definition from the managed object
   * definition.
   * <p>
@@ -655,6 +711,21 @@
  /**
   * Register a constraint with the managed object definition.
   * <p>
   * This method <b>must not</b> be called by applications.
   *
   * @param constraint
   *          The constraint to be registered.
   */
  protected final void registerConstraint(Constraint constraint) {
    constraints.add(constraint);
    allConstraints.add(constraint);
  }
  /**
   * Register a property definition with the managed object definition,
   * overriding any existing property definition with the same name.
   * <p>
opends/src/server/org/opends/server/admin/Constraint.java
New file
@@ -0,0 +1,76 @@
/*
 * 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;
import java.util.Collection;
import org.opends.server.admin.client.ClientConstraintHandler;
/**
 * An interface for enforcing constraints and dependencies between
 * managed objects and their properties. Constraints express
 * relationships between managed objects and their properties, for
 * example:
 * <ul>
 * <li>referential integrity: where one managed object references
 * another a constraint can enforce referential integrity. The
 * constraint can prevent creation of references to non-existent
 * managed objects, and also prevent deletion of referenced managed
 * objects
 * <li>property dependencies: for example, when a boolean property is
 * <code>true</code>, one or more additional properties must be
 * specified. This is useful for features like SSL, which when
 * enabled, requires that various SSL related configuration options
 * are specified
 * <li>property constraints: for example, when an upper limit
 * property must not have a value which is less than the lower limit
 * property.
 * </ul>
 * On the client-side constraints are enforced immediately before a
 * write operation is performed. That is to say, immediately before a
 * new managed object is created, changes to a managed object are
 * applied, or an existing managed object is deleted.
 */
public interface Constraint {
  /**
   * Gets the client-side constraint handlers which will be used to
   * enforce this constraint in client applications.
   *
   * @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
   *         be enforced on the server-side).
   */
  Collection<ClientConstraintHandler> getClientConstraintHandlers();
}
opends/src/server/org/opends/server/admin/client/ClientConstraintHandler.java
New file
@@ -0,0 +1,156 @@
/*
 * 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.client;
import java.util.Collection;
import org.opends.messages.Message;
import org.opends.server.admin.ManagedObjectPath;
/**
 * An interface for performing client-side constraint validation.
 * <p>
 * Constraints are evaluated immediately before the client performs a
 * write operation. If one or more constraints fails, the write
 * operation is refused and fails with an
 * {@link OperationRejectedException}.
 * <p>
 * A client constraint handler must override at least one of the
 * provided methods.
 *
 * @see org.opends.server.admin.Constraint
 */
public abstract class ClientConstraintHandler {
  /**
   * 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 context
   *          The management context.
   * @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 AuthorizationException
   *           If an authorization failure prevented this constraint
   *           from being evaluated.
   * @throws CommunicationException
   *           If a communications problem prevented this constraint
   *           from being evaluated.
   */
  public boolean isAddAcceptable(ManagementContext context,
      ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
      throws AuthorizationException, CommunicationException {
    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 context
   *          The management context.
   * @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 AuthorizationException
   *           If an authorization failure prevented this constraint
   *           from being evaluated.
   * @throws CommunicationException
   *           If a communications problem prevented this constraint
   *           from being evaluated.
   */
  public boolean isModifyAcceptable(ManagementContext context,
      ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
      throws AuthorizationException, CommunicationException {
    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 context
   *          The management context.
   * @param path
   *          The path of 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 AuthorizationException
   *           If an authorization failure prevented this constraint
   *           from being evaluated.
   * @throws CommunicationException
   *           If a communications problem prevented this constraint
   *           from being evaluated.
   */
  public boolean isDeleteAcceptable(ManagementContext context,
      ManagedObjectPath<?, ?> path, Collection<Message> unacceptableReasons)
      throws AuthorizationException, CommunicationException {
    return true;
  }
}
opends/src/server/org/opends/server/admin/client/ManagedObject.java
@@ -115,9 +115,9 @@
   *           this managed object is being modified but it has been
   *           removed from the server by another client.
   * @throws OperationRejectedException
   *           If the server refuses to add or modify this managed
   *           object due to some server-side constraint which cannot
   *           be satisfied.
   *           If this managed object cannot be added or modified due
   *           to some client-side or server-side constraint which
   *           cannot be satisfied.
   * @throws AuthorizationException
   *           If the server refuses to add or modify this managed
   *           object because the client does not have the correct
@@ -590,16 +590,16 @@
   *           If the managed object could not be removed because it
   *           could not found on the server.
   * @throws OperationRejectedException
   *           If the server refuses to remove the managed object due
   *           to some server-side constraint which cannot be
   *           If the managed object cannot be removed due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied (for example, if it is referenced by another
   *           managed object).
   * @throws ConcurrentModificationException
   *           If this managed object has been removed from the server
   *           by another client.
   * @throws AuthorizationException
   *           If the server refuses to make the list the managed
   *           objects because the client does not have the correct
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
@@ -631,16 +631,16 @@
   *           If the managed object could not be removed because it
   *           could not found on the server.
   * @throws OperationRejectedException
   *           If the server refuses to remove the managed object due
   *           to some server-side constraint which cannot be
   *           If the managed object cannot be removed due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied (for example, if it is referenced by another
   *           managed object).
   * @throws ConcurrentModificationException
   *           If this managed object has been removed from the server
   *           by another client.
   * @throws AuthorizationException
   *           If the server refuses to make the list the managed
   *           objects because the client does not have the correct
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
opends/src/server/org/opends/server/admin/client/ManagementContext.java
@@ -29,6 +29,19 @@
import java.util.SortedSet;
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.InstantiableRelationDefinition;
import org.opends.server.admin.ManagedObjectNotFoundException;
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.client.spi.Driver;
import org.opends.server.admin.std.client.RootCfgClient;
@@ -48,6 +61,232 @@
  /**
   * Deletes the named instantiable child managed object from 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.
   * @param name
   *          The name of the child managed object to be removed.
   * @return Returns <code>true</code> if the named instantiable
   *         child managed object was found, or <code>false</code>
   *         if it was not found.
   * @throws IllegalArgumentException
   *           If the relation definition is not associated with the
   *           parent managed object's definition.
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws OperationRejectedException
   *           If the managed object cannot be removed due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied (for example, if it is referenced by another
   *           managed object).
   * @throws AuthorizationException
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <C extends ConfigurationClient, S extends Configuration>
  boolean deleteManagedObject(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
      String name) throws IllegalArgumentException,
      ManagedObjectNotFoundException, OperationRejectedException,
      AuthorizationException, CommunicationException {
    return getDriver().deleteManagedObject(parent, rd, name);
  }
  /**
   * Deletes the optional child managed object from 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 optional relation definition.
   * @return Returns <code>true</code> if the optional child managed
   *         object was found, or <code>false</code> if it was not
   *         found.
   * @throws IllegalArgumentException
   *           If the relation definition is not associated with the
   *           parent managed object's definition.
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws OperationRejectedException
   *           If the managed object cannot be removed due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied (for example, if it is referenced by another
   *           managed object).
   * @throws AuthorizationException
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <C extends ConfigurationClient, S extends Configuration>
  boolean deleteManagedObject(
      ManagedObjectPath<?, ?> parent, OptionalRelationDefinition<C, S> rd)
      throws IllegalArgumentException, ManagedObjectNotFoundException,
      OperationRejectedException, AuthorizationException,
      CommunicationException {
    return getDriver().deleteManagedObject(parent, rd);
  }
  /**
   * 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 DefinitionDecodingException
   *           If the managed object was found but its type could not
   *           be determined.
   * @throws ManagedObjectDecodingException
   *           If the managed object was found but one or more of its
   *           properties could not be decoded.
   * @throws ManagedObjectNotFoundException
   *           If the requested managed object could not be found on
   *           the server.
   * @throws AuthorizationException
   *           If the server refuses to retrieve the managed object
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <C extends ConfigurationClient, S extends Configuration>
  ManagedObject<? extends C> getManagedObject(
      ManagedObjectPath<C, S> path) throws DefinitionDecodingException,
      ManagedObjectDecodingException, ManagedObjectNotFoundException,
      AuthorizationException, CommunicationException {
    return getDriver().getManagedObject(path);
  }
  /**
   * 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 DefinitionDecodingException
   *           If the managed object was found but its type could not
   *           be determined.
   * @throws PropertyException
   *           If the managed object was found but the requested
   *           property could not be decoded.
   * @throws ManagedObjectNotFoundException
   *           If the requested managed object could not be found on
   *           the server.
   * @throws AuthorizationException
   *           If the server refuses to retrieve the managed object
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <PD> PD getPropertyValue(ManagedObjectPath<?, ?> path,
      PropertyDefinition<PD> pd) throws IllegalArgumentException,
      DefinitionDecodingException, AuthorizationException,
      ManagedObjectNotFoundException, CommunicationException,
      PropertyException {
    return getDriver().getPropertyValue(path, pd);
  }
  /**
   * Gets the effective values of a property in the named managed
   * object.
   * <p>
   * Implementations MUST NOT not use
   * {@link #getManagedObject(ManagedObjectPath)} to read the
   * referenced managed object in its entirety. Specifically,
   * implementations MUST only attempt to resolve the default values
   * for the requested property and its dependencies (if it uses
   * inherited defaults). This is to avoid infinite recursion where a
   * managed object contains a property which inherits default values
   * from another property in the same 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 DefinitionDecodingException
   *           If the managed object was found but its type could not
   *           be determined.
   * @throws PropertyException
   *           If the managed object was found but the requested
   *           property could not be decoded.
   * @throws ManagedObjectNotFoundException
   *           If the requested managed object could not be found on
   *           the server.
   * @throws AuthorizationException
   *           If the server refuses to retrieve the managed object
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <PD> SortedSet<PD> getPropertyValues(
      ManagedObjectPath<?, ?> path, PropertyDefinition<PD> pd)
      throws IllegalArgumentException, DefinitionDecodingException,
      AuthorizationException, ManagedObjectNotFoundException,
      CommunicationException, PropertyException {
    return getDriver().getPropertyValues(path, pd);
  }
  /**
   * Gets the root configuration client associated with this
   * management context.
   *
@@ -67,7 +306,123 @@
   * @return Returns the root configuration managed object associated
   *         with this management context.
   */
  public abstract
  ManagedObject<RootCfgClient> getRootConfigurationManagedObject();
  public final
  ManagedObject<RootCfgClient> getRootConfigurationManagedObject() {
    return getDriver().getRootConfigurationManagedObject();
  }
  /**
   * 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.
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws AuthorizationException
   *           If the server refuses to list the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <C extends ConfigurationClient, S extends Configuration>
  String[] listManagedObjects(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd)
      throws IllegalArgumentException, ManagedObjectNotFoundException,
      AuthorizationException, CommunicationException {
    return getDriver().listManagedObjects(parent, rd);
  }
  /**
   * Lists the child managed objects of the named parent managed
   * object which are a sub-type of the specified managed object
   * definition.
   *
   * @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.
   * @param d
   *          The managed object definition.
   * @return Returns the names of the child managed objects which are
   *         a sub-type of the specified managed object definition.
   * @throws IllegalArgumentException
   *           If the relation definition is not associated with the
   *           parent managed object's definition.
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws AuthorizationException
   *           If the server refuses to list the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final <C extends ConfigurationClient, S extends Configuration>
  String[] listManagedObjects(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
      AbstractManagedObjectDefinition<? extends C, ? extends S> d)
      throws IllegalArgumentException, ManagedObjectNotFoundException,
      AuthorizationException, CommunicationException {
    return getDriver().listManagedObjects(parent, rd, d);
  }
  /**
   * 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.
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws AuthorizationException
   *           If the server refuses to make the determination because
   *           the client does not have the correct privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public final boolean managedObjectExists(ManagedObjectPath<?, ?> path)
      throws ManagedObjectNotFoundException, AuthorizationException,
      CommunicationException {
    return getDriver().managedObjectExists(path);
  }
  /**
   * Gets the driver associated with this management context.
   *
   * @return Returns the driver associated with this management
   *         context.
   */
  protected abstract Driver getDriver();
}
opends/src/server/org/opends/server/admin/client/OperationRejectedException.java
@@ -26,15 +26,29 @@
 */
package org.opends.server.admin.client;
import org.opends.messages.Message;
import static org.opends.messages.AdminMessages.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.util.Validator;
/**
 * This exception is thrown when the server refuses to create, delete,
 * or modify a managed object due to some server-side constraint that
 * cannot be satisified and which cannot be enforced by the client.
 * This exception is thrown when the client or server refuses to
 * create, delete, or modify a managed object due to one or more
 * constraints that cannot be satisfied.
 * <p>
 * Operations can be rejected either by a client-side constraint
 * violation triggered by {@link ClientConstraintHandler}, or by a
 * server-side error.
 * <p>
 * For example, the Directory Server might not be able perform an
 * operation due to some OS related problem, such as lack of disk
@@ -49,48 +63,81 @@
  /**
   * Create an operation rejected exception.
   */
  public OperationRejectedException() {
    // No implementation required.
  // Merge the messages into a single message.
  private static Message getSingleMessage(Collection<Message> messages) {
    Validator.ensureNotNull(messages);
    Validator.ensureTrue(!messages.isEmpty());
    MessageBuilder builder = new MessageBuilder();
    boolean isFirst = true;
    for (Message m : messages) {
      if (!isFirst) {
        builder.append("; ");
      }
      builder.append(m);
      isFirst = false;
    }
    return builder.toMessage();
  }
  // The messages describing the constraint violations that occurred.
  private final Collection<Message> messages;
  /**
   * Create an operation rejected exception with a cause.
   * Creates a new operation rejected exception with the provided
   * messages.
   *
   * @param cause
   *          The cause.
   * @param messages
   *          The messages describing the constraint violations that
   *          occurred (must be non-<code>null</code> and
   *          non-empty).
   */
  public OperationRejectedException(Throwable cause) {
    super(cause);
  public OperationRejectedException(Collection<Message> messages) {
    super(getSingleMessage(messages));
    this.messages = new ArrayList<Message>(messages);
  }
  /**
   * Create an operation rejected exception with a message and cause.
   * Creates a new operation rejected exception with the provided
   * message.
   *
   * @param message
   *          The message.
   * @param cause
   *          The cause.
   */
  public OperationRejectedException(Message message, Throwable cause) {
    super(message, cause);
  }
  /**
   * Create an operation rejected exception with a message.
   *
   * @param message
   *          The message.
   *          The message describing the constraint violation that
   *          occurred (must be non-<code>null</code> and
   *          non-empty).
   */
  public OperationRejectedException(Message message) {
    super(message);
    this(Collections.singleton(message));
  }
  /**
   * Creates a new operation rejected exception with a default
   * message.
   */
  public OperationRejectedException() {
    this(ERR_OPERATION_REJECTED_DEFAULT.get());
  }
  /**
   * Gets an unmodifiable collection view of the messages describing
   * the constraint violations that occurred.
   *
   * @return Returns an unmodifiable collection view of the messages
   *         describing the constraint violations that occurred.
   */
  public Collection<Message> getMessages() {
    return Collections.unmodifiableCollection(messages);
  }
}
opends/src/server/org/opends/server/admin/client/ldap/LDAPDriver.java
@@ -47,6 +47,7 @@
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import org.opends.messages.Message;
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
@@ -59,7 +60,6 @@
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectNotFoundException;
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;
@@ -74,6 +74,8 @@
import org.opends.server.admin.client.OperationRejectedException;
import org.opends.server.admin.client.spi.Driver;
import org.opends.server.admin.client.spi.PropertySet;
import org.opends.server.admin.std.client.RootCfgClient;
import org.opends.server.admin.std.meta.RootCfgDefn;
@@ -85,6 +87,9 @@
  // The LDAP connection.
  private final LDAPConnection connection;
  // The LDAP management context.
  private final LDAPManagementContext context;
  // The LDAP profile which should be used to construct LDAP
  // requests and decode LDAP responses.
  private final LDAPProfile profile;
@@ -95,12 +100,16 @@
   * Creates a new LDAP driver using the specified LDAP connection and
   * profile.
   *
   * @param context
   *          The LDAP management context.
   * @param connection
   *          The LDAP connection.
   * @param profile
   *          The LDAP profile.
   */
  public LDAPDriver(LDAPConnection connection, LDAPProfile profile) {
  public LDAPDriver(LDAPManagementContext context, LDAPConnection connection,
      LDAPProfile profile) {
    this.context = context;
    this.connection = connection;
    this.profile = profile;
  }
@@ -112,48 +121,6 @@
   */
  @Override
  public <C extends ConfigurationClient, S extends Configuration>
  boolean deleteManagedObject(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
      String name) throws IllegalArgumentException,
      ManagedObjectNotFoundException, OperationRejectedException,
      AuthorizationException, CommunicationException {
    validateRelationDefinition(parent, rd);
    if (!managedObjectExists(parent)) {
      throw new ManagedObjectNotFoundException();
    }
    return removeManagedObject(parent.child(rd, name));
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public <C extends ConfigurationClient, S extends Configuration>
  boolean deleteManagedObject(
      ManagedObjectPath<?, ?> parent, OptionalRelationDefinition<C, S> rd)
      throws IllegalArgumentException, ManagedObjectNotFoundException,
      OperationRejectedException, AuthorizationException,
      CommunicationException {
    validateRelationDefinition(parent, rd);
    if (!managedObjectExists(parent)) {
      throw new ManagedObjectNotFoundException();
    }
    return removeManagedObject(parent.child(rd));
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public <C extends ConfigurationClient, S extends Configuration>
  ManagedObject<? extends C> getManagedObject(
      ManagedObjectPath<C, S> path) throws DefinitionDecodingException,
      ManagedObjectDecodingException, ManagedObjectNotFoundException,
@@ -289,6 +256,18 @@
   * {@inheritDoc}
   */
  @Override
  public ManagedObject<RootCfgClient> getRootConfigurationManagedObject() {
    return new LDAPManagedObject<RootCfgClient>(this,
        RootCfgDefn.getInstance(), ManagedObjectPath.emptyPath(),
        new PropertySet(), true, null);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public <C extends ConfigurationClient, S extends Configuration>
  String[] listManagedObjects(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
@@ -341,7 +320,7 @@
      return true;
    }
    ManagedObjectPath<?,?> parent = path.parent();
    ManagedObjectPath<?, ?> parent = path.parent();
    LdapName dn = LDAPNameBuilder.create(parent, profile);
    if (!entryExists(dn)) {
      throw new ManagedObjectNotFoundException();
@@ -354,6 +333,43 @@
  /**
   * {@inheritDoc}
   */
  @Override
  protected <C extends ConfigurationClient, S extends Configuration>
  void deleteManagedObject(
      ManagedObjectPath<C, S> path) throws OperationRejectedException,
      AuthorizationException, CommunicationException {
    // Delete the entry and any subordinate entries.
    LdapName dn = LDAPNameBuilder.create(path, profile);
    try {
      connection.deleteSubtree(dn);
    } catch (OperationNotSupportedException e) {
      // Unwilling to perform.
      if (e.getMessage() != null) {
        throw new OperationRejectedException();
      } else {
        Message m = Message.raw("%s", e.getMessage());
        throw new OperationRejectedException(m);
      }
    } catch (NamingException e) {
      adaptNamingException(e);
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected LDAPManagementContext getManagementContext() {
    return context;
  }
  /**
   * Adapts a naming exception to an appropriate admin client
   * exception.
   *
@@ -543,42 +559,4 @@
    return d.resolveManagedObjectDefinition(resolver);
  }
  // Remove the named managed object.
  private boolean removeManagedObject(ManagedObjectPath<?, ?> path)
      throws CommunicationException, AuthorizationException,
      OperationRejectedException, ManagedObjectNotFoundException {
    if (!managedObjectExists(path)) {
      return false;
    }
    // Delete the entry and any subordinate entries.
    LdapName dn = LDAPNameBuilder.create(path, profile);
    try {
      connection.deleteSubtree(dn);
    } catch (OperationNotSupportedException e) {
      // Unwilling to perform.
      throw new OperationRejectedException(e);
    } catch (NamingException e) {
      adaptNamingException(e);
    }
    return true;
  }
  // Validate that a relation definition belongs to this managed
  // object.
  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/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java
@@ -40,6 +40,7 @@
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import org.opends.messages.Message;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
import org.opends.server.admin.InstantiableRelationDefinition;
@@ -59,8 +60,6 @@
import org.opends.server.admin.client.spi.Driver;
import org.opends.server.admin.client.spi.Property;
import org.opends.server.admin.client.spi.PropertySet;
import org.opends.server.admin.std.client.RootCfgClient;
import org.opends.server.admin.std.meta.RootCfgDefn;
@@ -74,22 +73,6 @@
final class LDAPManagedObject<T extends ConfigurationClient> extends
    AbstractManagedObject<T> {
  /**
   * Constructs a root LDAP managed object associated with the
   * provided LDAP driver.
   *
   * @param driver
   *          The LDAP management driver.
   * @return Returns a root LDAP managed object associated with the
   *         provided LDAP driver.
   */
  static ManagedObject<RootCfgClient> getRootManagedObject(
      LDAPDriver driver) {
    return new LDAPManagedObject<RootCfgClient>(driver, RootCfgDefn
        .getInstance(), ManagedObjectPath.emptyPath(), new PropertySet(), true,
        null);
  }
  // The LDAP management driver associated with this managed object.
  private final LDAPDriver driver;
@@ -176,7 +159,12 @@
          driver.getLDAPConnection().createEntry(dn, attributes);
        } catch (OperationNotSupportedException e) {
          // Unwilling to perform.
          throw new OperationRejectedException(e);
          if (e.getMessage() != null) {
            throw new OperationRejectedException();
          } else {
            Message m = Message.raw("%s", e.getMessage());
            throw new OperationRejectedException(m);
          }
        } catch (NamingException e) {
          driver.adaptNamingException(e);
        }
@@ -220,7 +208,12 @@
      throw new ManagedObjectAlreadyExistsException();
    } catch (OperationNotSupportedException e) {
      // Unwilling to perform.
      throw new OperationRejectedException(e);
      if (e.getMessage() != null) {
        throw new OperationRejectedException();
      } else {
        Message m = Message.raw("%s", e.getMessage());
        throw new OperationRejectedException(m);
      }
    } catch (NamingException e) {
      driver.adaptNamingException(e);
    }
@@ -269,7 +262,12 @@
        throw new AuthorizationException(e);
      } catch (OperationNotSupportedException e) {
        // Unwilling to perform.
        throw new OperationRejectedException(e);
        if (e.getMessage() != null) {
          throw new OperationRejectedException();
        } else {
          Message m = Message.raw("%s", e.getMessage());
          throw new OperationRejectedException(m);
        }
      } catch (NamingException e) {
        // Just treat it as a communication problem.
        throw new CommunicationException(e);
opends/src/server/org/opends/server/admin/client/ldap/LDAPManagementContext.java
@@ -30,9 +30,8 @@
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.admin.std.client.RootCfgClient;
import org.opends.server.admin.client.spi.Driver;
import org.opends.server.util.Validator;
@@ -63,7 +62,7 @@
  // Private constructor.
  private LDAPManagementContext(LDAPConnection connection,
      LDAPProfile profile) {
    this.driver = new LDAPDriver(connection, profile);
    this.driver = new LDAPDriver(this, connection, profile);
  }
@@ -71,7 +70,8 @@
  /**
   * {@inheritDoc}
   */
  public ManagedObject<RootCfgClient> getRootConfigurationManagedObject() {
    return LDAPManagedObject.getRootManagedObject(driver);
  @Override
  protected Driver getDriver() {
    return driver;
  }
}
opends/src/server/org/opends/server/admin/client/spi/AbstractManagedObject.java
@@ -36,9 +36,11 @@
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.messages.Message;
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.DefaultBehaviorException;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.IllegalPropertyValueException;
@@ -57,11 +59,13 @@
import org.opends.server.admin.RelationDefinition;
import org.opends.server.admin.SingletonRelationDefinition;
import org.opends.server.admin.client.AuthorizationException;
import org.opends.server.admin.client.ClientConstraintHandler;
import org.opends.server.admin.client.CommunicationException;
import org.opends.server.admin.client.ConcurrentModificationException;
import org.opends.server.admin.client.IllegalManagedObjectNameException;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.admin.client.MissingMandatoryPropertiesException;
import org.opends.server.admin.client.OperationRejectedException;
@@ -150,6 +154,30 @@
      throw new MissingMandatoryPropertiesException(exceptions);
    }
    // Now enforce any constraints.
    List<Message> messages = new LinkedList<Message>();
    boolean isAcceptable = true;
    ManagementContext context = getDriver().getManagementContext();
    for (Constraint constraint : definition.getAllConstraints()) {
      for (ClientConstraintHandler handler : constraint
          .getClientConstraintHandlers()) {
        if (existsOnServer) {
          if (!handler.isModifyAcceptable(context, this, messages)) {
            isAcceptable = false;
          }
        } else {
          if (!handler.isAddAcceptable(context, this, messages)) {
            isAcceptable = false;
          }
        }
      }
    }
    if (!isAcceptable) {
      throw new OperationRejectedException(messages);
    }
    // Commit the managed object.
    if (existsOnServer) {
      modifyExistingManagedObject();
@@ -497,8 +525,9 @@
   *           If the managed object's parent has been removed by
   *           another client.
   * @throws OperationRejectedException
   *           If the server refuses to add this managed object due to
   *           some server-side constraint which cannot be satisfied.
   *           If the managed object cannot be added due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied.
   * @throws AuthorizationException
   *           If the server refuses to add this managed object
   *           because the client does not have the correct
@@ -566,8 +595,8 @@
   *           If this managed object has been removed from the server
   *           by another client.
   * @throws OperationRejectedException
   *           If the server refuses to modify this managed object due
   *           to some server-side constraint which cannot be
   *           If the managed object cannot be added due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied.
   * @throws AuthorizationException
   *           If the server refuses to modify this managed object
opends/src/server/org/opends/server/admin/client/spi/Driver.java
@@ -32,15 +32,18 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
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.Constraint;
import org.opends.server.admin.DefaultBehaviorException;
import org.opends.server.admin.DefaultBehaviorProviderVisitor;
import org.opends.server.admin.DefinedDefaultBehaviorProvider;
@@ -55,14 +58,18 @@
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.client.AuthorizationException;
import org.opends.server.admin.client.ClientConstraintHandler;
import org.opends.server.admin.client.CommunicationException;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.admin.client.OperationRejectedException;
import org.opends.server.admin.std.client.RootCfgClient;
@@ -308,24 +315,28 @@
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws OperationRejectedException
   *           If the server refuses to remove the child managed
   *           object due to some server-side constraint which cannot
   *           be satisfied (for example, if it is referenced by
   *           another managed object).
   *           If the managed object cannot be removed due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied (for example, if it is referenced by another
   *           managed object).
   * @throws AuthorizationException
   *           If the server refuses to make the list the managed
   *           objects because the client does not have the correct
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public abstract <C extends ConfigurationClient, S extends Configuration>
  public final <C extends ConfigurationClient, S extends Configuration>
  boolean deleteManagedObject(
      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd,
      String name) throws IllegalArgumentException,
      ManagedObjectNotFoundException, OperationRejectedException,
      AuthorizationException, CommunicationException;
      AuthorizationException, CommunicationException {
    validateRelationDefinition(parent, rd);
    ManagedObjectPath<?, ?> child = parent.child(rd, name);
    return doDeleteManagedObject(child);
  }
@@ -352,24 +363,28 @@
   * @throws ManagedObjectNotFoundException
   *           If the parent managed object could not be found.
   * @throws OperationRejectedException
   *           If the server refuses to remove the child managed
   *           object due to some server-side constraint which cannot
   *           be satisfied (for example, if it is referenced by
   *           another managed object).
   *           If the managed object cannot be removed due to some
   *           client-side or server-side constraint which cannot be
   *           satisfied (for example, if it is referenced by another
   *           managed object).
   * @throws AuthorizationException
   *           If the server refuses to make the list the managed
   *           objects because the client does not have the correct
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  public abstract <C extends ConfigurationClient, S extends Configuration>
  public final <C extends ConfigurationClient, S extends Configuration>
  boolean deleteManagedObject(
      ManagedObjectPath<?, ?> parent, OptionalRelationDefinition<C, S> rd)
      throws IllegalArgumentException, ManagedObjectNotFoundException,
      OperationRejectedException, AuthorizationException,
      CommunicationException;
      CommunicationException {
    validateRelationDefinition(parent, rd);
    ManagedObjectPath<?, ?> child = parent.child(rd);
    return doDeleteManagedObject(child);
  }
@@ -507,6 +522,18 @@
  /**
   * Gets the root configuration managed object associated with this
   * management context driver.
   *
   * @return Returns the root configuration managed object associated
   *         with this management context driver.
   */
  public abstract
  ManagedObject<RootCfgClient> getRootConfigurationManagedObject();
  /**
   * Lists the child managed objects of the named parent managed
   * object.
   *
@@ -611,6 +638,40 @@
  /**
   * Deletes the named managed object.
   * <p>
   * Implementations do not need check whether the named managed
   * object exists, nor do they need to enforce client constraints.
   *
   * @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 path
   *          The path of the managed object to be deleted.
   * @throws OperationRejectedException
   *           If the managed object cannot be removed due to some
   *           server-side constraint which cannot be satisfied (for
   *           example, if it is referenced by another managed
   *           object).
   * @throws AuthorizationException
   *           If the server refuses to remove the managed objects
   *           because the client does not have the correct
   *           privileges.
   * @throws CommunicationException
   *           If the client cannot contact the server due to an
   *           underlying communication problem.
   */
  protected abstract <C extends ConfigurationClient, S extends Configuration>
  void deleteManagedObject(
      ManagedObjectPath<C, S> path) throws OperationRejectedException,
      AuthorizationException, CommunicationException;
  /**
   * Gets the default values for the specified property.
   *
   * @param <PD>
@@ -634,4 +695,82 @@
    return v.find(p, pd);
  }
  /**
   * Gets the management context associated with this driver.
   *
   * @return Returns the management context associated with this
   *         driver.
   */
  protected abstract ManagementContext getManagementContext();
  /**
   * Validate that a relation definition belongs to the managed object
   * referenced by the provided path.
   *
   * @param path
   *          The parent managed object path.
   * @param rd
   *          The relation definition.
   * @throws IllegalArgumentException
   *           If the relation definition does not belong to the
   *           managed object definition.
   */
  protected final 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());
    }
  }
  // Remove a managed object, first ensuring that the parent exists,
  // then ensuring that the child exists, before ensuring that any
  // constraints are satisfied.
  private <C extends ConfigurationClient, S extends Configuration>
  boolean doDeleteManagedObject(
      ManagedObjectPath<C, S> path) throws ManagedObjectNotFoundException,
      OperationRejectedException, AuthorizationException,
      CommunicationException {
    // First make sure that the parent exists.
    if (!managedObjectExists(path.parent())) {
      throw new ManagedObjectNotFoundException();
    }
    // Make sure that the targeted managed object exists.
    if (!managedObjectExists(path)) {
      return false;
    }
    // The targeted managed object is guaranteed to exist, so enforce
    // any constraints.
    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
    List<Message> messages = new LinkedList<Message>();
    boolean isAcceptable = true;
    for (Constraint constraint : d.getAllConstraints()) {
      for (ClientConstraintHandler handler : constraint
          .getClientConstraintHandlers()) {
        ManagementContext context = getManagementContext();
        if (!handler.isDeleteAcceptable(context, path, messages)) {
          isAcceptable = false;
        }
      }
    }
    if (!isAcceptable) {
      throw new OperationRejectedException(messages);
    }
    deleteManagedObject(path);
    return true;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestChildCfgDefn.java
@@ -180,6 +180,28 @@
  private TestChildCfgDefn() {
    super("test-child", null);
  }
  /**
   * Adds a constraint temporarily with this test definition.
   *
   * @param constraint The constraint.
   */
  public void addConstraint(Constraint constraint) {
    registerConstraint(constraint);
  }
  /**
   * Removes a constraint from this test definition.
   *
   * @param constraint The constraint.
   */
  public void removeConstraint(Constraint constraint) {
    deregisterConstraint(constraint);
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/LDAPClientTest.java
@@ -40,6 +40,7 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.AdminTestCase;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.ManagedObjectAlreadyExistsException;
@@ -797,6 +798,206 @@
  /**
   * Tests creation of a child managed object succeeds when registered
   * add constraints succeed.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test
  public void testAddConstraintSuccess() throws Exception {
    Constraint constraint = new MockConstraint(true, false, false);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
          "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      c.importLDIF(TEST_LDIF);
      c.addExpectedAttribute("cn", "test child new");
      c.addExpectedAttribute("objectclass", "top", "ds-cfg-virtual-attribute");
      c.addExpectedAttribute("ds-cfg-virtual-attribute-enabled", "true");
      c.addExpectedAttribute("ds-cfg-virtual-attribute-class",
          "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
      c.addExpectedAttribute("ds-cfg-virtual-attribute-type", "description");
      ManagementContext ctx = LDAPManagementContext.createFromContext(c);
      TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
      TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn
          .getInstance(), "test child new", null);
      child.setMandatoryBooleanProperty(true);
      child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer
          .getAttributeType("description"));
      child.commit();
      c.assertEntryIsCreated();
    } finally {
      // Clean up.
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
    }
  }
  /**
   * Tests creation of a child managed object fails when registered
   * add constraints fail.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test(expectedExceptions=OperationRejectedException.class)
  public void testAddConstraintFail() throws Exception {
    Constraint constraint = new MockConstraint(false, true, true);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
          "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      c.importLDIF(TEST_LDIF);
      c.addExpectedAttribute("cn", "test child new");
      c.addExpectedAttribute("objectclass", "top", "ds-cfg-virtual-attribute");
      c.addExpectedAttribute("ds-cfg-virtual-attribute-enabled", "true");
      c.addExpectedAttribute("ds-cfg-virtual-attribute-class",
          "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
      c.addExpectedAttribute("ds-cfg-virtual-attribute-type", "description");
      ManagementContext ctx = LDAPManagementContext.createFromContext(c);
      TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
      TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn
          .getInstance(), "test child new", null);
      child.setMandatoryBooleanProperty(true);
      child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer
          .getAttributeType("description"));
      child.commit();
      Assert.fail("The add constraint failed to prevent creation of the managed object");
    } finally {
      // Clean up.
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
    }
  }
  /**
   * Tests removal of a child managed object succeeds when registered
   * remove constraints succeed.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test
  public void testRemoveConstraintSuccess() throws Exception {
    Constraint constraint = new MockConstraint(false, false, true);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      DeleteSubtreeMockLDAPConnection c = new DeleteSubtreeMockLDAPConnection(
          "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      c.importLDIF(TEST_LDIF);
      ManagementContext ctx = LDAPManagementContext.createFromContext(c);
      TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
      parent.removeTestChild("test child 1");
      c.assertSubtreeIsDeleted();
    } finally {
      // Clean up.
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
    }
  }
  /**
   * Tests removal of a child managed object fails when registered
   * remove constraints fails.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test(expectedExceptions=OperationRejectedException.class)
  public void testRemoveConstraintFail() throws Exception {
    Constraint constraint = new MockConstraint(true, true, false);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      DeleteSubtreeMockLDAPConnection c = new DeleteSubtreeMockLDAPConnection(
          "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      c.importLDIF(TEST_LDIF);
      ManagementContext ctx = LDAPManagementContext.createFromContext(c);
      TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
      parent.removeTestChild("test child 1");
      Assert.fail("The remove constraint failed to prevent removal of the managed object");
    } finally {
      // Clean up.
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
    }
  }
  /**
   * Tests modification of a child managed object succeeds when
   * registered remove constraints succeed.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test
  public void testModifyConstraintSuccess() throws Exception {
    Constraint constraint = new MockConstraint(false, true, false);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
          "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      c.importLDIF(TEST_LDIF);
      c.addExpectedModification("ds-cfg-virtual-attribute-base-dn");
      ManagementContext ctx = LDAPManagementContext.createFromContext(c);
      TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
      TestChildCfgClient child = parent.getTestChild("test child 2");
      child.setOptionalMultiValuedDNProperty1(Collections.<DN> emptySet());
      child.commit();
      Assert.assertTrue(c.isEntryModified());
    } finally {
      // Clean up.
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
    }
  }
  /**
   * Tests modification of a child managed object fails when
   * registered remove constraints fails.
   *
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test(expectedExceptions = OperationRejectedException.class)
  public void testModifyConstraintFail() throws Exception {
    Constraint constraint = new MockConstraint(true, false, true);
    TestChildCfgDefn.getInstance().addConstraint(constraint);
    try {
      ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
          "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      c.importLDIF(TEST_LDIF);
      c.addExpectedModification("ds-cfg-virtual-attribute-base-dn");
      ManagementContext ctx = LDAPManagementContext.createFromContext(c);
      TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
      TestChildCfgClient child = parent.getTestChild("test child 2");
      child.setOptionalMultiValuedDNProperty1(Collections.<DN> emptySet());
      child.commit();
      Assert
          .fail("The modify constraint failed to prevent modification of the managed object");
    } finally {
      // Clean up.
      TestChildCfgDefn.getInstance().removeConstraint(constraint);
    }
  }
  // Asserts that the actual set of DNs contains the expected values.
  private void assertDNSetEquals(SortedSet<DN> actual, String... expected) {
    String[] actualStrings = new String[actual.size()];
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java
New file
@@ -0,0 +1,141 @@
/*
 * 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.client.ldap;
import java.util.Collection;
import java.util.Collections;
import org.opends.messages.Message;
import org.opends.server.admin.Constraint;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.client.AuthorizationException;
import org.opends.server.admin.client.ClientConstraintHandler;
import org.opends.server.admin.client.CommunicationException;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagementContext;
/**
 * A mock constraint which can be configured to refuse various types
 * of operation.
 */
public final class MockConstraint implements Constraint {
  /**
   * Mock client constraint handler.
   */
  private class Handler extends ClientConstraintHandler {
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAddAcceptable(ManagementContext context,
        ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
        throws AuthorizationException, CommunicationException {
      if (!allowAdds) {
        unacceptableReasons.add(Message.raw("Adds not allowed"));
      }
      return allowAdds;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDeleteAcceptable(ManagementContext context,
        ManagedObjectPath<?, ?> path, Collection<Message> unacceptableReasons)
        throws AuthorizationException, CommunicationException {
      if (!allowDeletes) {
        unacceptableReasons.add(Message.raw("Deletes not allowed"));
      }
      return allowDeletes;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isModifyAcceptable(ManagementContext context,
        ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
        throws AuthorizationException, CommunicationException {
      if (!allowModifies) {
        unacceptableReasons.add(Message.raw("Modifies not allowed"));
      }
      return allowModifies;
    }
  }
  // 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.<ClientConstraintHandler> singleton(new Handler());
  }
}