From f2886f88f7ebebea3a925d0e69bbb8f971ffbb3d Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 24 Sep 2007 20:38:02 +0000
Subject: [PATCH] Fix issue 1449: aggregation support.

---
 opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java |  473 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 405 insertions(+), 68 deletions(-)

diff --git a/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java b/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java
index fd15e40..c1e7b96 100644
--- a/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java
+++ b/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java
@@ -41,7 +41,12 @@
 import java.util.SortedSet;
 
 import org.opends.messages.Message;
+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.server.ConfigurationChangeListener;
 import org.opends.server.admin.server.ConfigurationDeleteListener;
 import org.opends.server.admin.server.ServerConstraintHandler;
@@ -143,6 +148,27 @@
 
 
     /**
+     * Registers a boolean "enabled" property in this managed object.
+     * When all the registered properties are true, the enabled
+     * property in the aggregated managed object must also be true.
+     * <p>
+     * By default no source properties are defined which indicates
+     * that the target property must always be true. When there is one
+     * or more source properties defined, a target property must also
+     * be defined.
+     *
+     * @param sourceEnabledPropertyName
+     *          The optional boolean "enabled" property in this
+     *          managed object.
+     */
+    public final void addSourceEnabledPropertyName(
+        String sourceEnabledPropertyName) {
+      this.sourceEnabledPropertyNames.add(sourceEnabledPropertyName);
+    }
+
+
+
+    /**
      * Sets the name of the managed object which is the parent of the
      * aggregated managed objects.
      * <p>
@@ -178,27 +204,6 @@
 
 
     /**
-     * Registers a boolean "enabled" property in this managed object.
-     * When all the registered properties are true, the enabled
-     * property in the aggregated managed object must also be true.
-     * <p>
-     * By default no source properties are defined which indicates
-     * that the target property must always be true. When there is one
-     * or more source properties defined, a target property must also
-     * be defined.
-     *
-     * @param sourceEnabledPropertyName
-     *          The optional boolean "enabled" property in this
-     *          managed object.
-     */
-    public final void addSourceEnabledPropertyName(
-        String sourceEnabledPropertyName) {
-      this.sourceEnabledPropertyNames.add(sourceEnabledPropertyName);
-    }
-
-
-
-    /**
      * Sets the optional boolean "enabled" property in the aggregated
      * managed object. This property must not be false while the
      * aggregated managed object is referenced.
@@ -240,8 +245,8 @@
       if (!sourceEnabledPropertyNames.isEmpty()
           && targetEnabledPropertyName == null) {
         throw new IllegalStateException(
-            "One or more source properties defined but " +
-            "target property is undefined");
+            "One or more source properties defined but "
+                + "target property is undefined");
       }
 
       return new AggregationPropertyDefinition<C, S>(d, propertyName, options,
@@ -393,42 +398,31 @@
    */
   private class ServerHandler extends ServerConstraintHandler {
 
-    // The associated property definition.
-    private final AggregationPropertyDefinition<C, S> pd;
-
-
-
-    // Creates a new server-side constraint handler.
-    private ServerHandler(AggregationPropertyDefinition<C, S> pd) {
-      this.pd = pd;
-    }
-
-
-
     /**
      * {@inheritDoc}
      */
     @Override
     public boolean isUsable(ServerManagedObject<?> managedObject,
         Collection<Message> unacceptableReasons) throws ConfigException {
-      SortedSet<String> names = managedObject.getPropertyValues(pd);
+      SortedSet<String> names = managedObject
+          .getPropertyValues(AggregationPropertyDefinition.this);
       ServerManagementContext context = ServerManagementContext.getInstance();
-      BooleanPropertyDefinition tpd = pd.getTargetEnabledPropertyDefinition();
-      List<BooleanPropertyDefinition> spdlist = pd
-          .getSourceEnabledPropertyDefinitions();
+      BooleanPropertyDefinition tpd = getTargetEnabledPropertyDefinition();
+      List<BooleanPropertyDefinition> spdlist =
+        getSourceEnabledPropertyDefinitions();
       Message thisUFN = managedObject.getManagedObjectDefinition()
           .getUserFriendlyName();
       String thisDN = managedObject.getDN().toString();
-      Message thatUFN = pd.getRelationDefinition().getUserFriendlyName();
+      Message thatUFN = getRelationDefinition().getUserFriendlyName();
 
       boolean isUsable = true;
       for (String name : names) {
-        ManagedObjectPath<C, S> path = pd.getChildPath(name);
+        ManagedObjectPath<C, S> path = getChildPath(name);
         String thatDN = path.toDN().toString();
 
         if (!context.managedObjectExists(path)) {
-          Message msg = ERR_SERVER_REFINT_DANGLING_REFERENCE.get(name, pd
-              .getName(), thisUFN, thisDN, thatUFN, thatDN);
+          Message msg = ERR_SERVER_REFINT_DANGLING_REFERENCE.get(name,
+              getName(), thisUFN, thisDN, thatUFN, thatDN);
           unacceptableReasons.add(msg);
           isUsable = false;
         } else if (tpd != null) {
@@ -448,15 +442,15 @@
 
             if (isRequired && !ref.getPropertyValue(tpd)) {
               Message msg = ERR_SERVER_REFINT_SOURCE_ENABLED_TARGET_DISABLED
-                  .get(name, pd.getName(), thisUFN, thisDN, thatUFN, thatDN);
+                  .get(name, getName(), thisUFN, thisDN, thatUFN, thatDN);
               unacceptableReasons.add(msg);
               isUsable = false;
             }
           } else {
             // Target must always be enabled.
             if (!ref.getPropertyValue(tpd)) {
-              Message msg = ERR_SERVER_REFINT_TARGET_DISABLED.get(name, pd
-                  .getName(), thisUFN, thisDN, thatUFN, thatDN);
+              Message msg = ERR_SERVER_REFINT_TARGET_DISABLED.get(name,
+                  getName(), thisUFN, thisDN, thatUFN, thatDN);
               unacceptableReasons.add(msg);
               isUsable = false;
             }
@@ -484,13 +478,13 @@
 
       // Add change and delete listeners against all referenced
       // components.
-      BooleanPropertyDefinition tpd = pd.getTargetEnabledPropertyDefinition();
-      List<BooleanPropertyDefinition> spdlist = pd
-          .getSourceEnabledPropertyDefinitions();
+      BooleanPropertyDefinition tpd = getTargetEnabledPropertyDefinition();
+      List<BooleanPropertyDefinition> spdlist =
+        getSourceEnabledPropertyDefinitions();
       Message thisUFN = managedObject.getManagedObjectDefinition()
           .getUserFriendlyName();
       String thisDN = managedObject.getDN().toString();
-      Message thatUFN = pd.getRelationDefinition().getUserFriendlyName();
+      Message thatUFN = getRelationDefinition().getUserFriendlyName();
 
       // Referenced managed objects will only need a change listener
       // if they have can be disabled.
@@ -510,7 +504,7 @@
       // Delete listeners need to be registered against the parent
       // entry of the referenced components.
       ServerManagementContext context = ServerManagementContext.getInstance();
-      ManagedObjectPath<?, ?> parentPath = pd.getParentPath();
+      ManagedObjectPath<?, ?> parentPath = getParentPath();
       ServerManagedObject<?> parent = context.getManagedObject(parentPath);
 
       // Create entries in the listener tables.
@@ -522,24 +516,25 @@
         new LinkedList<ReferentialIntegrityChangeListener>();
       changeListeners.put(managedObject.getDN(), clist);
 
-      for (String name : managedObject.getPropertyValues(pd)) {
-        ManagedObjectPath<C, S> path = pd.getChildPath(name);
+      for (String name : managedObject
+          .getPropertyValues(AggregationPropertyDefinition.this)) {
+        ManagedObjectPath<C, S> path = getChildPath(name);
         DN dn = path.toDN();
         String thatDN = dn.toString();
 
         // Register the delete listener.
-        Message msg = ERR_SERVER_REFINT_CANNOT_DELETE.get(thatUFN, thatDN, pd
-            .getName(), thisUFN, thisDN);
+        Message msg = ERR_SERVER_REFINT_CANNOT_DELETE.get(thatUFN, thatDN,
+            getName(), thisUFN, thisDN);
         ReferentialIntegrityDeleteListener dl =
           new ReferentialIntegrityDeleteListener(dn, msg);
-        parent.registerDeleteListener(pd.getRelationDefinition(), dl);
+        parent.registerDeleteListener(getRelationDefinition(), dl);
         dlist.add(dl);
 
         // Register the change listener if required.
         if (needsChangeListeners) {
           ServerManagedObject<? extends S> ref = context.getManagedObject(path);
-          msg = ERR_SERVER_REFINT_CANNOT_DISABLE.get(thatUFN, thatDN, pd
-              .getName(), thisUFN, thisDN);
+          msg = ERR_SERVER_REFINT_CANNOT_DISABLE.get(thatUFN, thatDN,
+              getName(), thisUFN, thisDN);
           ReferentialIntegrityChangeListener cl =
             new ReferentialIntegrityChangeListener(path, msg);
           ref.registerChangeListener(cl);
@@ -562,11 +557,11 @@
 
       // Delete listeners need to be deregistered against the parent
       // entry of the referenced components.
-      ManagedObjectPath<?, ?> parentPath = pd.getParentPath();
+      ManagedObjectPath<?, ?> parentPath = getParentPath();
       ServerManagedObject<?> parent = context.getManagedObject(parentPath);
       if (deleteListeners.containsKey(dn)) {
         for (ReferentialIntegrityDeleteListener dl : deleteListeners.get(dn)) {
-          parent.deregisterDeleteListener(pd.getRelationDefinition(), dl);
+          parent.deregisterDeleteListener(getRelationDefinition(), dl);
         }
         deleteListeners.remove(dn);
       }
@@ -601,6 +596,323 @@
 
 
   /**
+   * The client-side constraint handler implementation which enforces
+   * referential integrity when aggregating managed objects are added
+   * or modified.
+   */
+  private class SourceClientHandler extends ClientConstraintHandler {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAddAcceptable(ManagementContext context,
+        ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
+        throws AuthorizationException, CommunicationException {
+      // If all of this managed object's "enabled" properties are true
+      // then any referenced managed objects must also be enabled.
+      boolean needsEnabling = true;
+      for (BooleanPropertyDefinition spd :
+        getSourceEnabledPropertyDefinitions()) {
+        if (!managedObject.getPropertyValue(spd)) {
+          needsEnabling = false;
+        }
+      }
+
+      // Check the referenced managed objects exist and, if required,
+      // are enabled.
+      boolean isAcceptable = true;
+      BooleanPropertyDefinition tpd = getTargetEnabledPropertyDefinition();
+      Message ufn = getRelationDefinition().getUserFriendlyName();
+      for (String name : managedObject
+          .getPropertyValues(AggregationPropertyDefinition.this)) {
+        // Retrieve the referenced managed object and make sure it
+        // exists.
+        ManagedObjectPath<?, ?> path = getChildPath(name);
+        ManagedObject<?> ref;
+        try {
+          ref = context.getManagedObject(path);
+        } catch (DefinitionDecodingException e) {
+          Message msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name,
+              getName(), e.getMessageObject());
+          unacceptableReasons.add(msg);
+          isAcceptable = false;
+          continue;
+        } catch (ManagedObjectDecodingException e) {
+          Message msg = ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name,
+              getName(), e.getMessageObject());
+          unacceptableReasons.add(msg);
+          isAcceptable = false;
+          continue;
+        } catch (ManagedObjectNotFoundException e) {
+          Message msg = ERR_CLIENT_REFINT_TARGET_DANGLING_REFERENCE.get(ufn,
+              name, getName());
+          unacceptableReasons.add(msg);
+          isAcceptable = false;
+          continue;
+        }
+
+        // Make sure the reference managed object is enabled.
+        if (tpd != null && needsEnabling) {
+          if (!ref.getPropertyValue(tpd)) {
+            Message msg = ERR_CLIENT_REFINT_TARGET_DISABLED.get(ufn, name,
+                getName());
+            unacceptableReasons.add(msg);
+            isAcceptable = false;
+          }
+        }
+      }
+      return isAcceptable;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isModifyAcceptable(ManagementContext context,
+        ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
+        throws AuthorizationException, CommunicationException {
+      // The same constraint applies as for adds.
+      return isAddAcceptable(context, managedObject, unacceptableReasons);
+    }
+
+  }
+
+
+
+  /**
+   * The client-side constraint handler implementation which enforces
+   * referential integrity when aggregated managed objects are deleted
+   * or modified.
+   */
+  private class TargetClientHandler extends ClientConstraintHandler {
+
+    /**
+     * Instances of this class are used to search for all managed
+     * objects that contain a reference to the named managed object.
+     */
+    private class Finder implements
+        RelationDefinitionVisitor<Void, ManagedObject<?>> {
+
+      // Any authorization exceptions that were encountered.
+      private AuthorizationException ae = null;
+
+      // Any communication exceptions that were encountered.
+      private CommunicationException ce = null;
+
+      // The name of the managed object being deleted or modified.
+      private final String name;
+
+      // The collected list of referencing managed objects.
+      private final Collection<ManagedObject<?>> references;
+
+
+
+      // Private constructor.
+      private Finder(String name, Collection<ManagedObject<?>> references) {
+        this.name = name;
+        this.references = references;
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      public Void visitInstantiable(InstantiableRelationDefinition<?, ?> rd,
+          ManagedObject<?> p) {
+        try {
+          for (String childName : p.listChildren(rd)) {
+            find(p.getChild(rd, childName));
+          }
+        } catch (AuthorizationException e) {
+          ae = e;
+        } catch (CommunicationException e) {
+          ce = e;
+        } catch (OperationsException e) {
+          // Ignore all other types of exception.
+        }
+        return null;
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      public Void visitOptional(OptionalRelationDefinition<?, ?> rd,
+          ManagedObject<?> p) {
+        try {
+          find(p.getChild(rd));
+        } catch (AuthorizationException e) {
+          ae = e;
+        } catch (CommunicationException e) {
+          ce = e;
+        } catch (OperationsException e) {
+          // Ignore all other types of exception.
+        }
+        return null;
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      public Void visitSingleton(SingletonRelationDefinition<?, ?> rd,
+          ManagedObject<?> p) {
+        try {
+          find(p.getChild(rd));
+        } catch (AuthorizationException e) {
+          ae = e;
+        } catch (CommunicationException e) {
+          ce = e;
+        } catch (OperationsException e) {
+          // Ignore all other types of exception.
+        }
+        return null;
+      }
+
+
+
+      private void find(ManagedObject<?> current)
+          throws AuthorizationException, CommunicationException {
+        // First check the current managed object to see if it
+        // contains a reference.
+        ManagedObjectDefinition<?, ?> mod = current
+            .getManagedObjectDefinition();
+        if (mod.isChildOf(getManagedObjectDefinition())) {
+          for (String value : current
+              .getPropertyValues(AggregationPropertyDefinition.this)) {
+            if (compare(value, name) == 0) {
+              references.add(current);
+            }
+          }
+        }
+
+        // Now check its children.
+        for (RelationDefinition<?, ?> rd : mod.getAllRelationDefinitions()) {
+          rd.accept(this, current);
+
+          if (ae != null) {
+            throw ae;
+          }
+
+          if (ce != null) {
+            throw ce;
+          }
+        }
+      }
+
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isDeleteAcceptable(ManagementContext context,
+        ManagedObjectPath<?, ?> path, Collection<Message> unacceptableReasons)
+        throws AuthorizationException, CommunicationException {
+      // Any references to the deleted managed object should cause a
+      // constraint violation.
+      boolean isAcceptable = true;
+      for (ManagedObject<?> mo : findReferences(context, path.getName())) {
+        String name = mo.getManagedObjectPath().getName();
+        if (name == null) {
+          Message msg = ERR_CLIENT_REFINT_CANNOT_DELETE_WITHOUT_NAME.get(
+              getName(), mo.getManagedObjectDefinition().getUserFriendlyName(),
+              getManagedObjectDefinition().getUserFriendlyName());
+          unacceptableReasons.add(msg);
+        } else {
+          Message msg = ERR_CLIENT_REFINT_CANNOT_DELETE_WITH_NAME.get(
+              getName(), mo.getManagedObjectDefinition().getUserFriendlyName(),
+              name, getManagedObjectDefinition().getUserFriendlyName());
+          unacceptableReasons.add(msg);
+        }
+        isAcceptable = false;
+      }
+      return isAcceptable;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isModifyAcceptable(ManagementContext context,
+        ManagedObject<?> managedObject, Collection<Message> unacceptableReasons)
+        throws AuthorizationException, CommunicationException {
+      // If the modified managed object is disabled and there are some
+      // active references then refuse the change.
+      BooleanPropertyDefinition tpd = getTargetEnabledPropertyDefinition();
+
+      // The referenced managed object cannot be disabled: always ok.
+      if (tpd == null) {
+        return true;
+      }
+
+      // The referenced managed object is enabled: always ok.
+      if (managedObject.getPropertyValue(tpd)) {
+        return true;
+      }
+
+      // The referenced managed object is disabled. Need to check for
+      // active references.
+      boolean isAcceptable = true;
+      for (ManagedObject<?> mo : findReferences(context, managedObject
+          .getManagedObjectPath().getName())) {
+        boolean needsEnabling = true;
+        for (BooleanPropertyDefinition spd :
+          getSourceEnabledPropertyDefinitions()) {
+          if (!mo.getPropertyValue(spd)) {
+            needsEnabling = false;
+            break;
+          }
+        }
+
+        if (needsEnabling) {
+          String name = mo.getManagedObjectPath().getName();
+          if (name == null) {
+            Message msg = ERR_CLIENT_REFINT_CANNOT_DISABLE_WITHOUT_NAME.get(
+                managedObject.getManagedObjectDefinition()
+                    .getUserFriendlyName(), getName(), mo
+                    .getManagedObjectDefinition().getUserFriendlyName());
+            unacceptableReasons.add(msg);
+          } else {
+            Message msg = ERR_CLIENT_REFINT_CANNOT_DISABLE_WITH_NAME.get(
+                managedObject.getManagedObjectDefinition()
+                    .getUserFriendlyName(), getName(), mo
+                    .getManagedObjectDefinition().getUserFriendlyName(), name);
+            unacceptableReasons.add(msg);
+          }
+          isAcceptable = false;
+        }
+      }
+      return isAcceptable;
+    }
+
+
+
+    // Find all managed objects which reference the named managed
+    // object using this property.
+    private Collection<ManagedObject<?>> findReferences(
+        ManagementContext context, String name) throws AuthorizationException,
+        CommunicationException {
+      List<ManagedObject<?>> references = new LinkedList<ManagedObject<?>>();
+      Finder finder = new Finder(name, references);
+      finder.find(context.getRootConfigurationManagedObject());
+      return references;
+    }
+  }
+
+
+
+  /**
    * Creates an aggregation property definition builder.
    *
    * @param <C>
@@ -617,7 +929,7 @@
    * @return Returns the new aggregation property definition builder.
    */
   public static <C extends ConfigurationClient, S extends Configuration>
-  Builder<C, S> createBuilder(
+      Builder<C, S> createBuilder(
       AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
     return new Builder<C, S>(d, propertyName);
   }
@@ -760,8 +1072,8 @@
    * {@inheritDoc}
    */
   public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
-    // TODO: not yet implemented.
-    return Collections.emptyList();
+    ClientConstraintHandler handler = new SourceClientHandler();
+    return Collections.singleton(handler);
   }
 
 
@@ -796,7 +1108,7 @@
    * {@inheritDoc}
    */
   public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
-    ServerConstraintHandler handler = new ServerHandler(this);
+    ServerConstraintHandler handler = new ServerHandler();
     return Collections.singleton(handler);
   }
 
@@ -923,18 +1235,43 @@
       sourceEnabledProperties.add(BooleanPropertyDefinition.class.cast(pd));
     }
 
+    d = relationDefinition.getChildDefinition();
     if (targetEnabledPropertyName == null) {
       targetEnabledProperty = null;
     } else {
-      PropertyDefinition<?> pd;
-
-      d = relationDefinition.getChildDefinition();
-      pd = d.getPropertyDefinition(targetEnabledPropertyName);
+      PropertyDefinition<?> pd = d
+          .getPropertyDefinition(targetEnabledPropertyName);
 
       // Runtime cast is required to workaround a
       // bug in JDK versions prior to 1.5.0_08.
       targetEnabledProperty = BooleanPropertyDefinition.class.cast(pd);
     }
+
+    // Register a client-side constraint with the referenced
+    // definition. This will be used to enforce referential integrity
+    // for actions performed against referenced managed objects.
+    Constraint constraint = new Constraint() {
+
+      /**
+       * {@inheritDoc}
+       */
+      public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
+        ClientConstraintHandler handler = new TargetClientHandler();
+        return Collections.singleton(handler);
+      }
+
+
+
+      /**
+       * {@inheritDoc}
+       */
+      public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
+        return Collections.emptyList();
+      }
+
+    };
+
+    d.registerConstraint(constraint);
   }
 
 }

--
Gitblit v1.10.0