opendj-sdk/opends/src/server/org/opends/server/admin/server/CleanerConfigDeleteListener.java
New file @@ -0,0 +1,172 @@ /* * 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 static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.messages.MessageHandler.*; import java.util.HashMap; import java.util.Map; import org.opends.server.api.ConfigChangeListener; import org.opends.server.api.ConfigDeleteListener; 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.messages.AdminMessages; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; import org.opends.server.types.ResultCode; import org.opends.server.util.StaticUtils; /** * A configuration delete listener which detects when a specified * entry is removed and, when it is, cleans up any listeners * associated with it. */ final class CleanerConfigDeleteListener implements ConfigDeleteListener { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // The change listeners. private Map<DN, ConfigChangeListener> changeListeners = new HashMap<DN, ConfigChangeListener>(); // The DN of the monitored configuration entry. private final DN dn; /** * Creates a new cleaner configuration change listener which will * remove any registered listeners when then configuration entry it * is monitoring is removed. * * @param dn * The DN of the entry to be monitored. */ public CleanerConfigDeleteListener(DN dn) { this.dn = dn; } /** * Register a configuration change listener for removal when the * monitored entry is removed. * * @param dn * The name of the entry associated with the configuration * change listener. * @param listener * The configuration change listener. */ public void addConfigChangeListener(DN dn, ConfigChangeListener listener) { changeListeners.put(dn, listener); } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry) { // Remove the listeners if the deleted entry is the monitored // entry. if (configEntry.getDN().equals(dn)) { for (Map.Entry<DN, ConfigChangeListener> me : changeListeners.entrySet()) { ConfigEntry listenerConfigEntry = getConfigEntry(me.getKey()); if (listenerConfigEntry != null) { listenerConfigEntry.deregisterChangeListener(me.getValue()); } } // Now remove this listener as we are no longer needed. ConfigEntry parentConfigEntry = getConfigEntry(dn.getParent()); if (parentConfigEntry != null) { parentConfigEntry.deregisterDeleteListener(this); } } return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ public boolean configDeleteIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason) { // Always acceptable. return true; } // Returns the named configuration entry or null if it could not be // retrieved. private ConfigEntry getConfigEntry(DN dn) { try { ConfigEntry configEntry = DirectoryServer.getConfigEntry(dn); if (configEntry != null) { return configEntry; } else { int msgID = AdminMessages.MSGID_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST; String message = getMessage(msgID, String.valueOf(dn)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.MILD_ERROR, message, msgID); } } catch (ConfigException e) { // The dependent entry could not be retrieved. if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } int msgID = AdminMessages.MSGID_ADMIN_CANNOT_GET_MANAGED_OBJECT; String message = getMessage(msgID, String.valueOf(dn), StaticUtils .getExceptionMessage(e)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.MILD_ERROR, message, msgID); } return null; } } opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
@@ -175,7 +175,7 @@ ServerManagedObject<? extends S> mo; try { mo = ServerManagedObject.decode(childPath, r .getChildDefinition(), configEntry); .getChildDefinition(), configEntry, configEntry); } catch (DecodingException e) { generateUnacceptableReason(e, unacceptableReason); return false; opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
@@ -28,43 +28,194 @@ import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.messages.MessageHandler.*; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; 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.DecodingException; import org.opends.server.admin.DefaultBehaviorProvider; import org.opends.server.admin.DefaultBehaviorProviderVisitor; import org.opends.server.admin.DefinedDefaultBehaviorProvider; import org.opends.server.admin.ManagedObjectPath; import org.opends.server.admin.PropertyDefinition; import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider; import org.opends.server.admin.UndefinedDefaultBehaviorProvider; import org.opends.server.api.ConfigChangeListener; 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.messages.AdminMessages; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; import org.opends.server.util.StaticUtils; /** * An adaptor class which converts {@link ConfigChangeListener} * callbacks to strongly typed {@link ConfigurationChangeListener} * callbacks. * call-backs to strongly typed {@link ConfigurationChangeListener} * call-backs. * * @param <S> * The type of server configuration handled by the change * listener. */ final class ConfigChangeListenerAdaptor<S extends Configuration> extends AbstractConfigListenerAdaptor implements ConfigChangeListener { final class ConfigChangeListenerAdaptor<S extends Configuration> extends AbstractConfigListenerAdaptor implements ConfigChangeListener { // The managed object path. private final ManagedObjectPath path; /** * A default behavior visitor used for determining the set of * dependencies. * * @param <T> * The type of property. */ private static final class Visitor<T> implements DefaultBehaviorProviderVisitor<T, Void, ManagedObjectPath> { /** * Finds the dependencies associated with the provided property * definition. * * @param <T> * @param path * The current base path used for relative name * resolution. * @param pd * The property definition. * @param dependencies * Add dependencies names to this collection. */ public static <T> void find(ManagedObjectPath path, PropertyDefinition<T> pd, Collection<DN> dependencies) { Visitor<T> v = new Visitor<T>(dependencies); DefaultBehaviorProvider<T> db = pd.getDefaultBehaviorProvider(); db.accept(v, path); } // The names of entries that this change listener depends on. private final Collection<DN> dependencies; // Prevent instantiation. private Visitor(Collection<DN> dependencies) { this.dependencies = dependencies; } /** * {@inheritDoc} */ public Void visitAbsoluteInherited( AbsoluteInheritedDefaultBehaviorProvider<T> d, ManagedObjectPath p) { ManagedObjectPath next = d.getManagedObjectPath(); dependencies.add(DNBuilder.create(next)); // If the dependent property uses inherited defaults then // recursively get those as well. String propertyName = d.getPropertyName(); AbstractManagedObjectDefinition<?, ?> mod = d .getManagedObjectDefinition(); PropertyDefinition<?> pd = mod.getPropertyDefinition(propertyName); find(next, pd, dependencies); return null; } /** * {@inheritDoc} */ public Void visitAlias(AliasDefaultBehaviorProvider<T> d, ManagedObjectPath p) { return null; } /** * {@inheritDoc} */ public Void visitDefined(DefinedDefaultBehaviorProvider<T> d, ManagedObjectPath p) { return null; } /** * {@inheritDoc} */ public Void visitRelativeInherited( RelativeInheritedDefaultBehaviorProvider<T> d, ManagedObjectPath p) { ManagedObjectPath next = d.getManagedObjectPath(p); dependencies.add(DNBuilder.create(next)); // If the dependent property uses inherited defaults then // recursively get those as well. String propertyName = d.getPropertyName(); AbstractManagedObjectDefinition<?, ?> mod = d .getManagedObjectDefinition(); PropertyDefinition<?> pd = mod.getPropertyDefinition(propertyName); find(next, pd, dependencies); return null; } /** * {@inheritDoc} */ public Void visitUndefined(UndefinedDefaultBehaviorProvider<T> d, ManagedObjectPath p) { return null; } } /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // 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; // The listener used to notify this listener when dependency entries // are modified. private final DependencyConfigChangeListener dependencyListener; // The DN associated with this listener. private final DN dn; // The underlying change listener. private final ConfigurationChangeListener<? super S> listener; // Cached managed object between accept/apply callbacks. private ServerManagedObject<? extends S> cachedManagedObject; // The managed object path. private final ManagedObjectPath path; @@ -82,9 +233,47 @@ AbstractManagedObjectDefinition<?, S> d, ConfigurationChangeListener<? super S> listener) { this.path = path; this.dn = DNBuilder.create(path); this.d = d; this.listener = listener; this.cachedManagedObject = null; // This change listener should be notified when dependent entries // are modified. Determine the dependencies and register change // listeners against them. this.dependencies = new HashSet<DN>(); this.dependencyListener = new DependencyConfigChangeListener(dn, this); for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) { Visitor.find(path, pd, dependencies); } CleanerConfigDeleteListener cleaner = new CleanerConfigDeleteListener(dn); for (DN entryDN : dependencies) { // Be careful not to register listeners against the dependent // entry itself. if (!entryDN.equals(dn)) { ConfigEntry configEntry = getConfigEntry(entryDN); if (configEntry != null) { configEntry.registerChangeListener(dependencyListener); cleaner.addConfigChangeListener(entryDN, dependencyListener); } } } // Register a delete listener which will remove the dependency // listeners when this entry is removed. // FIXME: we should really remove the dependency listeners when // this listener is deregistered, but we have no way to track // that. DN parent = dn.getParent(); if (parent != null) { ConfigEntry configEntry = getConfigEntry(dn.getParent()); if (configEntry != null) { configEntry.registerDeleteListener(cleaner); } } } @@ -92,13 +281,33 @@ /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( ConfigEntry configEntry) { public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry) { return applyConfigurationChange(configEntry, configEntry); } /** * Attempts to apply a new configuration to this Directory Server * component based on the provided changed entry. * * @param configEntry * The configuration entry that containing the updated * configuration for this component. * @param newConfigEntry * The configuration entry that caused the notification * (will be different from <code>configEntry</code> if a * dependency was modified). * @return Information about the result of processing the * configuration change. */ public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry, ConfigEntry newConfigEntry) { // TODO: looking at the ConfigFileHandler implementation reveals // that this ConfigEntry will actually be a different object to // the one passed in the previous callback (it will have the same // content though). This config entry has the correct listener // lists. // the one passed in the previous call-back (it will have the same // content though). This configuration entry has the correct // listener lists. cachedManagedObject.setConfigEntry(configEntry); return listener.applyConfigurationChange(cachedManagedObject @@ -112,9 +321,36 @@ */ public boolean configChangeIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason) { try { cachedManagedObject = ServerManagedObject.decode(path, d, return configChangeIsAcceptable(configEntry, unacceptableReason, configEntry); } /** * Indicates whether the configuration entry that will result from a * proposed modification is acceptable to this change listener. * * @param configEntry * The configuration entry that will result from the * requested update. * @param unacceptableReason * A buffer to which this method can append a * human-readable message explaining why the proposed * change is not acceptable. * @param newConfigEntry * The configuration entry that caused the notification * (will be different from <code>configEntry</code> if a * dependency was modified). * @return <CODE>true</CODE> if the proposed entry contains an * acceptable configuration, or <CODE>false</CODE> if it * does not. */ public boolean configChangeIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason, ConfigEntry newConfigEntry) { try { cachedManagedObject = ServerManagedObject.decode(path, d, configEntry, newConfigEntry); } catch (DecodingException e) { generateUnacceptableReason(e, unacceptableReason); return false; @@ -133,7 +369,7 @@ /** * Get the configuiration change listener associated with this * Get the configuration change listener associated with this * adaptor. * * @return Returns the configuration change listener associated with @@ -142,4 +378,36 @@ ConfigurationChangeListener<? super S> getConfigurationChangeListener() { return listener; } // Returns the named configuration entry or null if it could not be // retrieved. private ConfigEntry getConfigEntry(DN dn) { try { ConfigEntry configEntry = DirectoryServer.getConfigEntry(dn); if (configEntry != null) { return configEntry; } else { int msgID = AdminMessages.MSGID_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST; String message = getMessage(msgID, String.valueOf(dn)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR, message, msgID); } } catch (ConfigException e) { // The dependent entry could not be retrieved. if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } int msgID = AdminMessages.MSGID_ADMIN_CANNOT_GET_MANAGED_OBJECT; String message = getMessage(msgID, String.valueOf(dn), StaticUtils .getExceptionMessage(e)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR, message, msgID); } return null; } } opendj-sdk/opends/src/server/org/opends/server/admin/server/DependencyConfigChangeListener.java
New file @@ -0,0 +1,153 @@ /* * 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 static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.messages.MessageHandler.*; import org.opends.server.api.ConfigChangeListener; 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.messages.AdminMessages; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; import org.opends.server.types.ResultCode; import org.opends.server.util.StaticUtils; /** * A configuration change listener which can be used to notify a * change listener when modifications are made to configuration * entries that it depends upon. */ final class DependencyConfigChangeListener implements ConfigChangeListener { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // The DN of the dependent configuration entry. private final DN dependentDN; // The dependent configuration change listener adaptor. private final ConfigChangeListenerAdaptor<?> dependentListener; /** * Creates a new dependency configuration change listener which will * notify the dependent listener whenever the configuration entry * that this listener monitors is modified. * * @param dependentDN * The DN of the dependent configuration entry. * @param dependentListener * The dependent configuration change listener adaptor. */ public DependencyConfigChangeListener(DN dependentDN, ConfigChangeListenerAdaptor<?> dependentListener) { this.dependentDN = dependentDN; this.dependentListener = dependentListener; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry) { ConfigEntry dependentConfigEntry = getConfigEntry(dependentDN); if (dependentConfigEntry != null) { return dependentListener.applyConfigurationChange(dependentConfigEntry, configEntry); } else { // The dependent entry was not found. configEntry.deregisterChangeListener(this); return new ConfigChangeResult(ResultCode.SUCCESS, false); } } /** * {@inheritDoc} */ public boolean configChangeIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason) { ConfigEntry dependentConfigEntry = getConfigEntry(dependentDN); if (dependentConfigEntry != null) { return dependentListener.configChangeIsAcceptable(dependentConfigEntry, unacceptableReason, configEntry); } else { // The dependent entry was not found. configEntry.deregisterChangeListener(this); return true; } } // Returns the named configuration entry or null if it could not be // retrieved. private ConfigEntry getConfigEntry(DN dn) { try { ConfigEntry configEntry = DirectoryServer.getConfigEntry(dn); if (configEntry != null) { return configEntry; } else { int msgID = AdminMessages.MSGID_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST; String message = getMessage(msgID, String.valueOf(dn)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.MILD_ERROR, message, msgID); } } catch (ConfigException e) { // The dependent entry could not be retrieved. if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } int msgID = AdminMessages.MSGID_ADMIN_CANNOT_GET_MANAGED_OBJECT; String message = getMessage(msgID, String.valueOf(dn), StaticUtils .getExceptionMessage(e)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.MILD_ERROR, message, msgID); } return null; } } opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
@@ -396,6 +396,40 @@ 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. @@ -410,7 +444,7 @@ for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) { List<String> values = getAttribute(mod, pd, configEntry); try { decodeProperty(properties, path, pd, values, configEntry); decodeProperty(properties, path, pd, values, newConfigEntry); } catch (PropertyException e) { exceptions.add(e); } opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java
@@ -581,13 +581,11 @@ * change listener. This test makes sure that a component is * notified when the default values it inherits from another * component are modified. * <p> * FIXME: disabled - waiting for fix to issue 1793. * * @throws Exception * If the test unexpectedly fails. */ @Test(enabled = false) @Test public void testChangeListenerChildValues4() throws Exception { TestParentCfg parent = getParent("test parent 1");