/* * 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.messages.MessageHandler.getMessage; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.opends.server.admin.ClassPropertyDefinition; import org.opends.server.admin.Configuration; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.messages.AdminMessages; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.InitializationException; import org.opends.server.types.ResultCode; /** * A skeleton implementation of a configuration manager. This type of * manager can be used for managing "optional" configurations (i.e. * where there is a one to zero or one relationship). *

* Configuration managers are responsible for initializing and * finalizing instances as required. During initialization, the * manager takes responsibility for loading and instantiating the * implementation class. It then initializes the implementation * instance by invoking a method having the following signature: * *

 * void initializeXXX(YYY config) throws ConfigException,
 *     InitializationException;
 * 
* * Where XXX is the simple name of the instance type * T, and YYY is the expected * configuration type for the implementation, which is either the * configuration type C or a sub-type thereof. * * @param * The type of configuration referenced by this manager. * @param * The type of component represented by the configuration. */ public abstract class AbstractOptionalConfigurationManager { /** * Private add listener implementation. */ private class AddListener implements ConfigurationAddListener { // Private constructor. private AddListener() { // No implementation required. } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationAdd(C config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList messages = new ArrayList(); // We have committed to this change so always update the // configuration. setConfiguration(config); if (isEnabled(config)) { // The configuration is enabled so it should be instantiated. try { // Notify that a new instance has been added. doRegisterInstance(getImplementation(config)); } catch (ConfigException e) { messages.add(e.getMessage()); resultCode = DirectoryServer.getServerErrorResultCode(); } catch (InitializationException e) { messages.add(e.getMessage()); resultCode = DirectoryServer.getServerErrorResultCode(); } } else { // Ignore this configuration if it is disabled - we don't need // to set instance to null here since it should already be // null, but we do just to make the behavior explicit. if (instance != null) { finalizeInstance(instance); instance = null; } notifyDisableInstance(config); } // Return the configuration result. return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * {@inheritDoc} */ public boolean isConfigurationAddAcceptable(C config, List unacceptableReasons) { if (isEnabled(config)) { // It's enabled so always validate the class. return isJavaClassAcceptable(config, unacceptableReasons); } else { // It's disabled so ignore it. return true; } } } /** * Private change listener implementation. */ private class ChangeListener implements ConfigurationChangeListener { // Private constructor. private ChangeListener() { // No implementation required. } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange(C config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList messages = new ArrayList(); // We have committed to this change so always update the // configuration. setConfiguration(config); // See whether the configuration should be enabled. if (instance == null) { if (isEnabled(config)) { // The configuration is enabled so it should be // instantiated. try { // Notify that a new instance has been added. doRegisterInstance(getImplementation(config)); } catch (ConfigException e) { messages.add(e.getMessage()); resultCode = DirectoryServer.getServerErrorResultCode(); } catch (InitializationException e) { messages.add(e.getMessage()); resultCode = DirectoryServer.getServerErrorResultCode(); } } else { // Do nothing: we could notify that the configuration is // disabled, but it was already, so there's no point in // notifying again. } } else if (isEnabled(config)) { // The instance is currently active, so we don't need to do // anything. Changes to the class name should not be applied // dynamically, so if the class name did change then // indicate that administrative action is required for that // change to take effect. String className = getJavaImplementationClass(config); if (!className.equals(instance.getClass().getName())) { adminActionRequired = true; } } else { // We need to disable the instance. if (instance != null) { finalizeInstance(instance); instance = null; } notifyDisableInstance(config); } // Return the configuration result. return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable(C config, List unacceptableReasons) { if (isEnabled(config)) { // It's enabled so always validate the class. return isJavaClassAcceptable(config, unacceptableReasons); } else if (isEnabled(getConfiguration())) { return isDisableInstanceAcceptable(config, unacceptableReasons); } else { // It's already disabled so ignore it. return true; } } } /** * Private delete listener implementation. */ private class DeleteListener implements ConfigurationDeleteListener { // Private constructor. private DeleteListener() { // No implementation required. } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationDelete(C config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; // We have committed to this change so always update the // configuration and finalize the instance. setConfiguration(null); if (instance != null) { finalizeInstance(instance); instance = null; } notifyDeleteInstance(config); return new ConfigChangeResult(resultCode, adminActionRequired); } /** * {@inheritDoc} */ public boolean isConfigurationDeleteAcceptable(C config, List unacceptableReasons) { if (isEnabled(getConfiguration())) { return isDisableInstanceAcceptable(config, unacceptableReasons); } else { return true; } } } // The property definition defining the Java implementation class. private final ClassPropertyDefinition propertyDefinition; // The instance class. private final Class theClass; // Configuration add listener. private final AddListener addListener; // Configuration change listener. private final ChangeListener changeListener; // Configuration delete listener. private final DeleteListener deleteListener; // Indicates whether or not the parent listeners have been // registered. private boolean listenersRegistered; // The current active configuration. private C currentConfig; // The current active instance. private T instance; /** * Create a new optional configuration manager. * * @param theClass * The instance class. * @param propertyDefinition * The property definition defining the Java implementation * class. */ protected AbstractOptionalConfigurationManager(Class theClass, ClassPropertyDefinition propertyDefinition) { this.currentConfig = null; this.instance = null; this.theClass = theClass; this.propertyDefinition = propertyDefinition; this.listenersRegistered = false; this.addListener = new AddListener(); this.deleteListener = new DeleteListener(); this.changeListener = new ChangeListener(); } /** * Performs any finalization that may be necessary for this manager. */ public final void finalizeManager() { deregisterAddListener(addListener); deregisterDeleteListener(deleteListener); if (currentConfig != null) { deregisterChangeListener(changeListener, currentConfig); } if (instance != null) { finalizeInstance(instance); } } /** * Get the current active configuration. * * @return Returns the current active configuration. */ public final C getConfiguration() { return currentConfig; } /** * Get the current active instance. * * @return Returns the current active instance. */ public final T getInstance() { return instance; } /** * Initializes this manager based on the information in the provided * configuration if available. The implementation of this method * will first register this manager as an add/delete listener before * processing the provided initial configuration if it is available. *

* It is safe to initialize the manager more than once. This is * useful during testing as an easy way to set the manager's * configuration. * * @param config * The configuration (can be null if there * is no initial configuration. * @throws ConfigException * If there is a problem with the configuration. * @throws InitializationException * If a problem occurs during initialization. */ public final void initialize(C config) throws ConfigException, InitializationException { // Register to be notified when a configuration is added or // removed (only do this once). if (!listenersRegistered) { registerAddListener(addListener); registerDeleteListener(deleteListener); listenersRegistered = true; } // Make sure that there is no current instance. instance = null; if (config != null) { // Always register as a listener. setConfiguration(config); if (isEnabled(config)) { // We have a configuration and it is enabled. doRegisterInstance(getImplementation(config)); } else { // There is a configuration present but it is disabled. Don't // process the configuration in case it is invalid. We don't // want to issue warnings for components which are disabled. notifyDisableInstance(config); } } } /** * Deregisters an add listener. * * @param listener * The configuration add listener. */ protected abstract void deregisterAddListener( ConfigurationAddListener listener); /** * Deregisters a change listener. * * @param listener * The configuration change listener. * @param config * The configuration from which to deregister the change * listener. */ protected abstract void deregisterChangeListener( ConfigurationChangeListener listener, C config); /** * Deregisters a delete listener. * * @param listener * The configuration delete listener. */ protected abstract void deregisterDeleteListener( ConfigurationDeleteListener listener); /** * Finalizes a previously activate instance. * * @param instance * The instance to be finalized. */ protected abstract void finalizeInstance(T instance); /** * Get the name of the Java implementation class. *

* Sub-classes should usually implement this method using a call to * the configuration's getJavaImplementationClass() * method. * * @param config * The active configuration. * @return Returns the name of the Java implementation class. */ protected abstract String getJavaImplementationClass(C config); /** * Indicates whether the active instance can be disabled or deleted. * * @param config * The configuration that will be disabled or deleted. * @param unacceptableReasons * A list that can be used to hold messages about why the * active instance cannot be disabled or deleted.. * @return Returns true if the active instance can be * disabled or deleted. */ protected abstract boolean isDisableInstanceAcceptable(C config, List unacceptableReasons); /** * Determines whether or not the active configuration is enabled or * not. *

* Sub-classes should usually implement this method using a call to * the configuration's isEnabled() method. * * @param config * The active configuration. * @return Returns true if the configuration is * enabled. */ protected abstract boolean isEnabled(C config); /** * Notify that the configuration has been deleted. This may involve * logging a message. Implementations do not need to worry about * finalizing the instance, since that is performed by the * {@link #finalizeInstance(Object)} method. * * @param config * The deleted configuration. */ protected abstract void notifyDeleteInstance(C config); /** * Notify that the configuration has been disabled. This may involve * logging a message. Implementations do not need to worry about * finalizing the instance, since that is performed by the * {@link #finalizeInstance(Object)} method. * * @param config * The disabled configuration. */ protected abstract void notifyDisableInstance(C config); /** * Register to be notified when the configuration is added. * * @param listener * The configuration add listener. * @throws ConfigException * If the add listener could not be registered. */ protected abstract void registerAddListener( ConfigurationAddListener listener) throws ConfigException; /** * Register to be notified when a configuration is changed. * * @param listener * The configuration change listener. * @param config * Receive change notifications for this configuration. */ protected abstract void registerChangeListener( ConfigurationChangeListener listener, C config); /** * Register to be notified when the configuration is deleted. * * @param listener * The configuration delete listener. * @throws ConfigException * If the add listener could not be registered. */ protected abstract void registerDeleteListener( ConfigurationDeleteListener listener) throws ConfigException; /** * Registers a newly activated instance. This may involve updating * the parent configuration, starting a thread, etc. This manager * will have already invoked initialize(C config) * against the instance. * * @param instance * The new instance. * @throws InitializationException * If a problem occurs during initialization. */ protected abstract void registerInstance(T instance) throws InitializationException; // Notify listeners that a new instance has been added and/or // enabled. private void doRegisterInstance(T instance) throws InitializationException { this.instance = instance; // Let sub-class implementation decide what to do with the new // instance. registerInstance(instance); } // Load, instantiate, and initialize the class named in the Java // implementation class property of the provided configuration. private T getImplementation(C config) throws ConfigException { String className = getJavaImplementationClass(config); // Load the class and cast it to a T. Class implClass; T instance; try { implClass = propertyDefinition.loadClass(className, theClass); instance = implClass.newInstance(); } catch (Exception e) { int msgID = AdminMessages.MSGID_ADMIN_CANNOT_INSTANTIATE_CLASS; String message = getMessage(msgID, String.valueOf(className), String.valueOf(config.dn()), stackTraceToSingleLineString(e)); throw new ConfigException(msgID, message, e); } // Perform the necessary initialization for the instance. try { // Determine the initialization method to use: it must take a // single parameter which is the exact type of the configuration // object. String name = getInitializationMethodName(); Method method = implClass.getMethod(name, config.definition() .getServerConfigurationClass()); method.invoke(instance, config); } catch (Exception e) { int msgID = AdminMessages.MSGID_ADMIN_CANNOT_INITIALIZE_COMPONENT; String message = getMessage(msgID, String.valueOf(className), String.valueOf(config.dn()), stackTraceToSingleLineString(e)); throw new ConfigException(msgID, message, e); } // The instance has been successfully initialized. return instance; } // Get the name of the method which should be used // to initialize instances. private String getInitializationMethodName() { return "initialize" + theClass.getSimpleName(); } // Determines whether or not the new configuration's implementation // class is acceptable. private boolean isJavaClassAcceptable(C config, List unacceptableReasons) { String className = getJavaImplementationClass(config); // Load the class and cast it to a T. Class implClass; try { implClass = propertyDefinition.loadClass(className, theClass); implClass.newInstance(); } catch (Exception e) { int msgID = AdminMessages.MSGID_ADMIN_CANNOT_INSTANTIATE_CLASS; unacceptableReasons.add(getMessage(msgID, String .valueOf(className), String.valueOf(config.dn()), stackTraceToSingleLineString(e))); return false; } // Perform the necessary initialization for the instance. try { // Determine the initialization method to use: it must take a // single parameter which is the exact type of the configuration // object. String name = getInitializationMethodName(); implClass.getMethod(name, config.definition() .getServerConfigurationClass()); } catch (Exception e) { int msgID = AdminMessages.MSGID_ADMIN_CANNOT_INITIALIZE_COMPONENT; unacceptableReasons.add(getMessage(msgID, String .valueOf(className), String.valueOf(config.dn()), stackTraceToSingleLineString(e))); return false; } // The class is valid as far as we can tell. return true; } // Set the new configuration and register for change events. private void setConfiguration(C config) { // We only need to register for change events if there was no // previous configuration. This is because the notification // frameworks ensures that listeners are preserved when a new // configuration replaces an old one. if (currentConfig == null && config != null) { registerChangeListener(changeListener, config); } currentConfig = config; } }