/* * 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 2006 Sun Microsystems, Inc. */ package org.opends.server.core; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; import org.opends.server.api.ConfigAddListener; import org.opends.server.api.ConfigChangeListener; import org.opends.server.api.ConfigDeleteListener; import org.opends.server.api.SynchronizationProvider; import org.opends.server.config.BooleanConfigAttribute; import org.opends.server.config.ConfigEntry; import org.opends.server.config.ConfigException; import org.opends.server.config.StringConfigAttribute; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchFilter; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.Debug.*; import static org.opends.server.loggers.Error.*; import static org.opends.server.messages.ConfigMessages.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; /** * This class defines a utility that will be used to manage the configuration * for the set of synchronization providers configured in the Directory Server. * It will perform the necessary initialization of those synchronization * providers when the server is first started, and then will manage any changes * to them while the server is running. */ public class SynchronizationProviderConfigManager implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener { /** * The fully-qualified name of this class for debugging purposes. */ private static final String CLASS_NAME = "org.opends.server.core.SynchronizationProviderConfigManager"; // The mapping between configuration entry DNs and their corresponding // synchronization provider implementations. private ConcurrentHashMap registeredProviders; // The DN of the associated configuration entry. private DN configEntryDN; /** * Creates a new instance of this synchronization provider config manager. */ public SynchronizationProviderConfigManager() { assert debugConstructor(CLASS_NAME); // No implementation is required. } /** * Initializes the configuration associated with the Directory Server * synchronization providers. This should only be called at Directory Server * startup. * * @throws ConfigException If a critical configuration problem prevents any * of the synchronization providers from starting * properly. * * @throws InitializationException If a problem occurs while initializing * any of the synchronization providers that * is not related to the Directory Server * configuration. */ public void initializeSynchronizationProviders() throws ConfigException, InitializationException { assert debugEnter(CLASS_NAME, "initializeSynchronizationProviders"); registeredProviders = new ConcurrentHashMap(); // Get the configuration entry that is the parent for all synchronization // providers in the server. ConfigEntry providerRoot; try { configEntryDN = DN.decode(DN_SYNCHRONIZATION_PROVIDER_BASE); providerRoot = DirectoryServer.getConfigEntry(configEntryDN); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeSynchronizationProviders", e); int msgID = MSGID_CONFIG_SYNCH_CANNOT_GET_CONFIG_BASE; String message = getMessage(msgID, stackTraceToSingleLineString(e)); throw new ConfigException(msgID, message, e); } // If the configuration root entry is null, then assume it doesn't exist. // In that case, then fail. At least that entry must exist in the // configuration, even if there are no synchronization providers defined // below it. if (providerRoot == null) { int msgID = MSGID_CONFIG_SYNCH_BASE_DOES_NOT_EXIST; String message = getMessage(msgID); throw new ConfigException(msgID, message); } // Register as an add and delete listener for the base entry so that we can // be notified if new providers are added or existing providers are removed. providerRoot.registerAddListener(this); providerRoot.registerDeleteListener(this); // Iterate through the set of immediate children below the provider root // entry and register those providers. for (ConfigEntry providerEntry : providerRoot.getChildren().values()) { DN providerDN = providerEntry.getDN(); // Register as a change listener for this provider entry so that we will // be notified of any changes that may be made to it. providerEntry.registerChangeListener(this); // Check to see if this entry appears to contain a synchronization // provider configuration. If not, then fail. try { SearchFilter providerFilter = SearchFilter.createFilterFromString("(objectClass=" + OC_SYNCHRONIZATION_PROVIDER + ")"); if (! providerFilter.matchesEntry(providerEntry.getEntry())) { int msgID = MSGID_CONFIG_SYNCH_ENTRY_DOES_NOT_HAVE_PROVIDER_CONFIG; String message = getMessage(msgID, String.valueOf(providerDN)); throw new ConfigException(msgID, message); } } catch (Exception e) { assert debugException(CLASS_NAME, "initializeSynchronizationProviders", e); int msgID = MSGID_CONFIG_SYNCH_CANNOT_CHECK_FOR_PROVIDER_CONFIG_OC; String message = getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } // See if the entry contains an attribute that indicates whether the // synchronization provider should be enabled. If it does not, then fail. // If it is present but set to false, then log a warning and skip it. int msgID = MSGID_CONFIG_SYNCH_DESCRIPTION_PROVIDER_ENABLED; BooleanConfigAttribute enabledStub = new BooleanConfigAttribute(ATTR_SYNCHRONIZATION_PROVIDER_ENABLED, getMessage(msgID), true); try { BooleanConfigAttribute enabledAttr = (BooleanConfigAttribute) providerEntry.getConfigAttribute(enabledStub); if (enabledAttr == null) { msgID = MSGID_CONFIG_SYNCH_PROVIDER_NO_ENABLED_ATTR; String message = getMessage(msgID, String.valueOf(providerDN)); throw new ConfigException(msgID, message); } else if (! enabledAttr.activeValue()) { msgID = MSGID_CONFIG_SYNCH_PROVIDER_DISABLED; String message = getMessage(msgID, String.valueOf(providerDN)); logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_WARNING, message, msgID); continue; } } catch (Exception e) { assert debugException(CLASS_NAME, "initializeSynchronizationProviders", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_DETERMINE_ENABLED_STATE; String message = getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } // See if the entry contains an attribute that specifies the class name // for the synchronization provider implementation. If there is no such // attribute, then fail. String providerClassName; msgID = MSGID_CONFIG_SYNCH_DESCRIPTION_PROVIDER_CLASS; StringConfigAttribute classStub = new StringConfigAttribute(ATTR_SYNCHRONIZATION_PROVIDER_CLASS, getMessage(msgID), true, false, true); try { StringConfigAttribute classAttr = (StringConfigAttribute) providerEntry.getConfigAttribute(classStub); if (classAttr == null) { msgID = MSGID_CONFIG_SYNCH_NO_CLASS_ATTR; String message = getMessage(msgID, String.valueOf(providerDN)); throw new ConfigException(msgID, message); } else { providerClassName = classAttr.activeValue(); } } catch (Exception e) { assert debugException(CLASS_NAME, "initializeSynchronizationProviders", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_DETERMINE_CLASS; String message = getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } // Load the specified provider class. If an error occurs, then fail. Class providerClass; try { // FIXME -- Should we use a custom class loader for this? providerClass = Class.forName(providerClassName); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeSynchronizationProviders", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS; String message = getMessage(msgID, String.valueOf(providerClassName), String.valueOf(providerDN), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } // Make sure that the specified class is a valid synchronization provider. // If not, then fail. SynchronizationProvider provider; try { provider = (SynchronizationProvider) providerClass.newInstance(); } catch (Exception e) { assert debugException(CLASS_NAME, "initializeSynchronizationProviders", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER; String message = getMessage(msgID, String.valueOf(providerClassName), String.valueOf(providerDN), stackTraceToSingleLineString(e)); throw new InitializationException(msgID, message, e); } // Perform the necessary initialization for the synchronization provider. // If a problem occurs, then fail. try { provider.initializeSynchronizationProvider(providerEntry); } catch (ConfigException ce) { msgID = MSGID_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER; String message = getMessage(msgID, String.valueOf(providerDN), ce.getMessage()); throw new ConfigException(msgID, message, ce); } catch (InitializationException ie) { msgID = MSGID_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER; String message = getMessage(msgID, String.valueOf(providerDN), ie.getMessage()); throw new InitializationException(msgID, message, ie); } catch (Exception e) { msgID = MSGID_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER; String message = getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e)); throw new ConfigException(msgID, message, e); } // Register the synchronization provider with the Directory Server. DirectoryServer.registerSynchronizationProvider(provider); // Put this provider in the hash so that we will be able to find it if it // is altered. registeredProviders.put(providerDN, provider); } } /** * 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. * * @return true if the proposed entry contains an acceptable * configuration, or false if it does not. */ public boolean configChangeIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason) { assert debugEnter(CLASS_NAME, "configChangeIsAcceptable", String.valueOf(configEntry), "java.lang.StringBuilder"); DN providerDN = configEntry.getDN(); SynchronizationProvider provider = registeredProviders.get(providerDN); // Check to see if this entry appears to contain a backend configuration. // If not, then reject it. try { SearchFilter providerFilter = SearchFilter.createFilterFromString("(objectClass=" + OC_SYNCHRONIZATION_PROVIDER + ")"); if (! providerFilter.matchesEntry(configEntry.getEntry())) { int msgID = MSGID_CONFIG_SYNCH_ENTRY_DOES_NOT_HAVE_PROVIDER_CONFIG; unacceptableReason.append(getMessage(msgID, String.valueOf(providerDN))); return false; } } catch (Exception e) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", e); int msgID = MSGID_CONFIG_SYNCH_CANNOT_CHECK_FOR_PROVIDER_CONFIG_OC; unacceptableReason.append(getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e))); return false; } // See if the entry contains an attribute that indicates whether the // provider should be enabled. If it does not, then reject it. int msgID = MSGID_CONFIG_SYNCH_DESCRIPTION_PROVIDER_ENABLED; BooleanConfigAttribute enabledStub = new BooleanConfigAttribute(ATTR_SYNCHRONIZATION_PROVIDER_ENABLED, getMessage(msgID), true); try { BooleanConfigAttribute enabledAttr = (BooleanConfigAttribute) configEntry.getConfigAttribute(enabledStub); if (enabledAttr == null) { msgID = MSGID_CONFIG_SYNCH_PROVIDER_NO_ENABLED_ATTR; unacceptableReason.append(getMessage(msgID, String.valueOf(providerDN))); return false; } } catch (Exception e) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_DETERMINE_ENABLED_STATE; unacceptableReason.append(getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e))); return false; } // See if the entry contains an attribute that specifies the provider class. // If it does not, then fail. String className; msgID = MSGID_CONFIG_SYNCH_DESCRIPTION_PROVIDER_CLASS; StringConfigAttribute classStub = new StringConfigAttribute(ATTR_SYNCHRONIZATION_PROVIDER_CLASS, getMessage(msgID), true, false, true); try { StringConfigAttribute classAttr = (StringConfigAttribute) configEntry.getConfigAttribute(classStub); if (classAttr == null) { msgID = MSGID_CONFIG_SYNCH_NO_CLASS_ATTR; unacceptableReason.append(getMessage(msgID, String.valueOf(providerDN))); return false; } else { className = classAttr.pendingValue(); } } catch (Exception e) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_DETERMINE_CLASS; unacceptableReason.append(getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e))); return false; } // If the provider is currently disabled, or if the class is different from // the one used by the running provider, then make sure that it is // acceptable. if ((provider == null) || (! className.equals(provider.getClass().getName()))) { Class providerClass; try { // FIXME -- Should we use a custom class loader for this? providerClass = Class.forName(className); } catch (Exception e) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS; unacceptableReason.append(getMessage(msgID, String.valueOf(className), String.valueOf(providerDN), stackTraceToSingleLineString(e))); return false; } try { SynchronizationProvider newProvider = (SynchronizationProvider) providerClass.newInstance(); } catch (Exception e) { assert debugException(CLASS_NAME, "configChangeIsAcceptable", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER; unacceptableReason.append(getMessage(msgID, String.valueOf(className), String.valueOf(providerDN), stackTraceToSingleLineString(e))); return false; } } // If we've gotten to this point, then it is acceptable as far as we are // concerned. If it is unacceptable according to the configuration for that // synchronization provider, then the provider itself will need to make that // determination. return true; } /** * 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. * * @return Information about the result of processing the configuration * change. */ public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry) { assert debugEnter(CLASS_NAME, "applyConfigurationChange", String.valueOf(configEntry)); DN providerDN = configEntry.getDN(); SynchronizationProvider provider = registeredProviders.get(providerDN); ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList messages = new ArrayList(); // Check to see if this entry appears to contain a synchronization provider // configuration. If not, then fail. try { SearchFilter providerFilter = SearchFilter.createFilterFromString("(objectClass=" + OC_SYNCHRONIZATION_PROVIDER + ")"); if (! providerFilter.matchesEntry(configEntry.getEntry())) { int msgID = MSGID_CONFIG_SYNCH_ENTRY_DOES_NOT_HAVE_PROVIDER_CONFIG; messages.add(getMessage(msgID, String.valueOf(providerDN))); if (resultCode == ResultCode.SUCCESS) { resultCode = ResultCode.CONSTRAINT_VIOLATION; } } } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); int msgID = MSGID_CONFIG_SYNCH_CANNOT_CHECK_FOR_PROVIDER_CONFIG_OC; messages.add(getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e))); if (resultCode == ResultCode.SUCCESS) { resultCode = DirectoryServer.getServerErrorResultCode(); } } // See if the entry contains an attribute that indicates whether the // provider should be enabled. If it does not, then reject it. boolean shouldEnable = false; int msgID = MSGID_CONFIG_SYNCH_DESCRIPTION_PROVIDER_ENABLED; BooleanConfigAttribute enabledStub = new BooleanConfigAttribute(ATTR_SYNCHRONIZATION_PROVIDER_ENABLED, getMessage(msgID), true); try { BooleanConfigAttribute enabledAttr = (BooleanConfigAttribute) configEntry.getConfigAttribute(enabledStub); if (enabledAttr == null) { msgID = MSGID_CONFIG_SYNCH_PROVIDER_NO_ENABLED_ATTR; messages.add(getMessage(msgID, String.valueOf(providerDN))); if (resultCode == ResultCode.SUCCESS) { resultCode = ResultCode.CONSTRAINT_VIOLATION; } } else { shouldEnable = enabledAttr.pendingValue(); } } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_DETERMINE_ENABLED_STATE; messages.add(getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e))); if (resultCode == ResultCode.SUCCESS) { resultCode = DirectoryServer.getServerErrorResultCode(); } } // See if the entry contains an attribute that specifies the provider class. // If it does not, then reject it. String className = null; msgID = MSGID_CONFIG_SYNCH_DESCRIPTION_PROVIDER_CLASS; StringConfigAttribute classStub = new StringConfigAttribute(ATTR_SYNCHRONIZATION_PROVIDER_CLASS, getMessage(msgID), true, false, true); try { StringConfigAttribute classAttr = (StringConfigAttribute) configEntry.getConfigAttribute(classStub); if (classAttr == null) { msgID = MSGID_CONFIG_SYNCH_NO_CLASS_ATTR; messages.add(getMessage(msgID, String.valueOf(providerDN))); if (resultCode == ResultCode.SUCCESS) { resultCode = ResultCode.CONSTRAINT_VIOLATION; } } else { className = classAttr.pendingValue(); } } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_DETERMINE_CLASS; messages.add(getMessage(msgID, String.valueOf(providerDN), stackTraceToSingleLineString(e))); if (resultCode == ResultCode.SUCCESS) { resultCode = DirectoryServer.getServerErrorResultCode(); } } // If the provider is currently disabled, or if the class is different from // the one used by the running provider, then make sure that it is // acceptable. SynchronizationProvider newProvider = null; if ((resultCode == ResultCode.SUCCESS) && ((provider == null) || (! provider.getClass().getName().equals(className)))) { Class providerClass = null; try { // FIXME -- Should we use a custom class loader for this? providerClass = Class.forName(className); } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS; messages.add(getMessage(msgID, String.valueOf(className), String.valueOf(providerDN), stackTraceToSingleLineString(e))); if (resultCode == ResultCode.SUCCESS) { resultCode = DirectoryServer.getServerErrorResultCode(); } } try { if (providerClass != null) { newProvider = (SynchronizationProvider) providerClass.newInstance(); } } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); msgID = MSGID_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER; messages.add(getMessage(msgID, String.valueOf(className), String.valueOf(providerDN), stackTraceToSingleLineString(e))); if (resultCode == ResultCode.SUCCESS) { resultCode = DirectoryServer.getServerErrorResultCode(); } } } // If everything looks OK, then process the configuration change. if (resultCode == ResultCode.SUCCESS) { // If the provider is currently disabled but should be enabled, then do // so now. if (provider == null) { if (shouldEnable && (newProvider != null)) { try { newProvider.initializeSynchronizationProvider(configEntry); registeredProviders.put(configEntryDN, newProvider); } catch (Exception e) { assert debugException(CLASS_NAME, "applyConfigurationChange", e); msgID = MSGID_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER; messages.add(getMessage(msgID, String.valueOf(configEntryDN), stackTraceToSingleLineString(e))); if (resultCode == ResultCode.SUCCESS) { resultCode = DirectoryServer.getServerErrorResultCode(); } } } } // Otherwise, see if the enabled flag or class name changed and indicate // that it will require a restart to take effect. else { if (! shouldEnable) { msgID = MSGID_CONFIG_SYNCH_PROVIDER_HAS_BEEN_DISABLED; messages.add(getMessage(msgID, String.valueOf(configEntryDN))); adminActionRequired = true; } if (! provider.getClass().getName().equals(className)) { msgID = MSGID_CONFIG_SYNCH_PROVIDER_CLASS_CHANGED; messages.add(getMessage(msgID, String.valueOf(configEntryDN), String.valueOf(provider.getClass().getName()), String.valueOf(className))); adminActionRequired = true; } } } return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * Indicates whether the configuration entry that will result from a proposed * add is acceptable to this add listener. * * @param configEntry The configuration entry that will result from * the requested add. * @param unacceptableReason A buffer to which this method can append a * human-readable message explaining why the * proposed entry is not acceptable. * * @return true if the proposed entry contains an acceptable * configuration, or false if it does not. */ public boolean configAddIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason) { assert debugEnter(CLASS_NAME, "configAddIsAcceptable", String.valueOf(configEntry), "java.lang.StringBuilder"); // NYI return true; } /** * Attempts to apply a new configuration based on the provided added entry. * * @param configEntry The new configuration entry that contains the * configuration to apply. * * @return Information about the result of processing the configuration * change. */ public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry) { assert debugEnter(CLASS_NAME, "applyConfigurationAdd", String.valueOf(configEntry)); // NYI return null; } /** * Indicates whether it is acceptable to remove the provided configuration * entry. * * @param configEntry The configuration entry that will be removed * from the configuration. * @param unacceptableReason A buffer to which this method can append a * human-readable message explaining why the * proposed delete is not acceptable. * * @return true if the proposed entry may be removed from the * configuration, or false if not. */ public boolean configDeleteIsAcceptable(ConfigEntry configEntry, StringBuilder unacceptableReason) { assert debugEnter(CLASS_NAME, "configDeleteIsAcceptable", String.valueOf(configEntry), "java.lang.StringBuilder"); // NYI return true; } /** * Attempts to apply a new configuration based on the provided deleted entry. * * @param configEntry The new configuration entry that has been deleted. * * @return Information about the result of processing the configuration * change. */ public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry) { assert debugEnter(CLASS_NAME, "applyConfigurationDelete", String.valueOf(configEntry)); // NYI return null; } }