/* * 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 * * * Copyright 2007-2008 Sun Microsystems, Inc. */ package org.opends.server.admin.server; import static org.opends.server.loggers.debug.DebugLogger.*; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.opends.messages.AdminMessages; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; 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.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; import org.opends.server.admin.UndefinedDefaultBehaviorProvider; 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.ErrorLogger; import org.opends.server.loggers.debug.DebugTracer; 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; /** * An adaptor class which converts {@link ConfigChangeListener} * call-backs to {@link ServerManagedObjectChangeListener} * call-backs. * * @param <S> * The type of server configuration handled by the change * listener. */ final class ConfigChangeListenerAdaptor<S extends Configuration> extends AbstractConfigListenerAdaptor implements ConfigChangeListener { /** * 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 delete listener which is used to remove this listener and any // dependencies. private final ConfigDeleteListener cleanerListener; // 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 ConfigChangeListener dependencyListener; // The DN associated with this listener. private final DN dn; // The underlying change listener. private final ServerManagedObjectChangeListener<? super S> listener; // The managed object path. private final ManagedObjectPath<?, S> path; /** * Create a new configuration change listener adaptor. * * @param path * The managed object path. * @param listener * The underlying change listener. */ public ConfigChangeListenerAdaptor(ManagedObjectPath<?, S> path, ServerManagedObjectChangeListener<? super S> listener) { this.path = path; this.dn = DNBuilder.create(path); 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 ConfigChangeListener() { public ConfigChangeResult applyConfigurationChange( ConfigEntry configEntry) { ConfigEntry dependentConfigEntry = getConfigEntry(dn); if (dependentConfigEntry != null) { return ConfigChangeListenerAdaptor.this .applyConfigurationChange(dependentConfigEntry); } else { // The dependent entry was not found. configEntry.deregisterChangeListener(this); return new ConfigChangeResult(ResultCode.SUCCESS, false); } } public boolean configChangeIsAcceptable(ConfigEntry configEntry, MessageBuilder unacceptableReason) { ConfigEntry dependentConfigEntry = getConfigEntry(dn); if (dependentConfigEntry != null) { return ConfigChangeListenerAdaptor.this.configChangeIsAcceptable( dependentConfigEntry, unacceptableReason, configEntry); } else { // The dependent entry was not found. configEntry.deregisterChangeListener(this); return true; } } }; AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition(); for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) { Visitor.find(path, pd, dependencies); } 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); } } } // Register a delete listener against the parent which will // finalize this change listener when the monitored configuration // entry is removed. this.cleanerListener = new ConfigDeleteListener() { public ConfigChangeResult applyConfigurationDelete( ConfigEntry configEntry) { // Perform finalization if the deleted entry is the monitored // entry. if (configEntry.getDN().equals(dn)) { finalizeChangeListener(); } return new ConfigChangeResult(ResultCode.SUCCESS, false); } public boolean configDeleteIsAcceptable(ConfigEntry configEntry, MessageBuilder unacceptableReason) { // Always acceptable. return true; } }; DN parent = dn.getParent(); if (parent != null) { ConfigEntry configEntry = getConfigEntry(dn.getParent()); if (configEntry != null) { configEntry.registerDeleteListener(cleanerListener); } } } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry) { // Looking at the ConfigFileHandler implementation reveals // that this ConfigEntry will actually be a different object to // 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); ConfigChangeResult result = listener .applyConfigurationChange(cachedManagedObject); // 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.performPostModify(cachedManagedObject); } catch (ConfigException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } } return result; } /** * {@inheritDoc} */ public boolean configChangeIsAcceptable(ConfigEntry configEntry, MessageBuilder unacceptableReason) { 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, MessageBuilder unacceptableReason, ConfigEntry newConfigEntry) { try { ServerManagementContext context = ServerManagementContext.getInstance(); cachedManagedObject = context.decode(path, configEntry, newConfigEntry); } catch (DecodingException e) { unacceptableReason.append(e.getMessageObject()); return false; } // Give up immediately if a constraint violation occurs. try { cachedManagedObject.ensureIsUsable(); } catch (ConstraintViolationException e) { generateUnacceptableReason(e.getMessages(), unacceptableReason); return false; } // Let the change listener decide. List<Message> reasons = new LinkedList<Message>(); if (listener.isConfigurationChangeAcceptable(cachedManagedObject,reasons)) { return true; } else { generateUnacceptableReason(reasons, unacceptableReason); return false; } } /** * Finalizes this configuration change listener adaptor. This method * must be called before this change listener is removed. */ public void finalizeChangeListener() { // Remove the dependency listeners. for (DN dependency : dependencies) { ConfigEntry listenerConfigEntry = getConfigEntry(dependency); if (listenerConfigEntry != null) { listenerConfigEntry.deregisterChangeListener(dependencyListener); } } // Now remove the cleaner listener as it will no longer be // needed. ConfigEntry parentConfigEntry = getConfigEntry(dn.getParent()); if (parentConfigEntry != null) { parentConfigEntry.deregisterDeleteListener(cleanerListener); } } /** * Get the server managed object change listener associated with * this adaptor. * * @return Returns the server managed object change listener * associated with this adaptor. */ ServerManagedObjectChangeListener<? super S> getServerManagedObjectChangeListener() { 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 { Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST .get(String.valueOf(dn)); ErrorLogger.logError(message); } } catch (ConfigException e) { // The dependent entry could not be retrieved. if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message message = AdminMessages.ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get( String.valueOf(dn), StaticUtils.getExceptionMessage(e)); ErrorLogger.logError(message); } return null; } }