/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.admin.server;
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.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
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.forgerock.opendj.config.server.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.opends.server.types.DN;
import org.forgerock.opendj.ldap.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<>(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;
}
}
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** 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<>();
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();
}
}
public boolean configChangeIsAcceptable(ConfigEntry configEntry,
LocalizableMessageBuilder 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();
}
public boolean configDeleteIsAcceptable(ConfigEntry configEntry,
LocalizableMessageBuilder unacceptableReason) {
// Always acceptable.
return true;
}
};
DN parent = dn.parent();
if (parent != null) {
ConfigEntry configEntry = getConfigEntry(dn.parent());
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) {
logger.traceException(e);
}
}
}
}
return result;
}
/** {@inheritDoc} */
public boolean configChangeIsAcceptable(ConfigEntry configEntry,
LocalizableMessageBuilder 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,
LocalizableMessageBuilder 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<LocalizableMessage> reasons = new LinkedList<>();
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.parent());
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 {
logger.error(AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.get(dn));
}
} catch (ConfigException e) {
// The dependent entry could not be retrieved.
logger.traceException(e);
logger.error(AdminMessages.ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(
dn, StaticUtils.getExceptionMessage(e)));
}
return null;
}
}