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