001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import static org.opends.messages.ConfigMessages.*;
020import static org.opends.server.util.StaticUtils.*;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.concurrent.ConcurrentHashMap;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.opendj.config.ClassPropertyDefinition;
028import org.forgerock.opendj.config.server.ConfigChangeResult;
029import org.forgerock.opendj.config.server.ConfigException;
030import org.forgerock.opendj.config.server.ConfigurationAddListener;
031import org.forgerock.opendj.config.server.ConfigurationChangeListener;
032import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
033import org.forgerock.opendj.ldap.DN;
034import org.forgerock.opendj.server.config.meta.AccountStatusNotificationHandlerCfgDefn;
035import org.forgerock.opendj.server.config.server.AccountStatusNotificationHandlerCfg;
036import org.forgerock.opendj.server.config.server.RootCfg;
037import org.forgerock.util.Utils;
038import org.opends.server.api.AccountStatusNotificationHandler;
039import org.opends.server.types.InitializationException;
040
041/**
042 * This class defines a utility that will be used to manage the set of account
043 * status notification handlers defined in the Directory Server.  It will
044 * initialize the handlers when the server starts, and then will manage any
045 * additions, removals, or modifications to any notification handlers while the
046 * server is running.
047 */
048public class AccountStatusNotificationHandlerConfigManager
049       implements
050          ConfigurationChangeListener <AccountStatusNotificationHandlerCfg>,
051          ConfigurationAddListener    <AccountStatusNotificationHandlerCfg>,
052          ConfigurationDeleteListener <AccountStatusNotificationHandlerCfg>
053{
054  /** A mapping between the DNs of the config entries and the associated notification handlers. */
055  private final ConcurrentHashMap<DN, AccountStatusNotificationHandler<?>> notificationHandlers;
056
057  private final ServerContext serverContext;
058
059  /**
060   * Creates a new instance of this account status notification handler config
061   * manager.
062   * @param serverContext
063   *            The server context.
064   */
065  public AccountStatusNotificationHandlerConfigManager(ServerContext serverContext)
066  {
067    this.serverContext = serverContext;
068    notificationHandlers = new ConcurrentHashMap<>();
069  }
070
071  /**
072   * Initializes all account status notification handlers currently defined in
073   * the Directory Server configuration.  This should only be called at
074   * Directory Server startup.
075   *
076   * @throws  ConfigException  If a configuration problem causes the
077   *                           notification handler initialization process to
078   *                           fail.
079   *
080   * @throws  InitializationException  If a problem occurs while initializing
081   *                                   the account status notification handlers
082   *                                   that is not related to the server
083   *                                   configuration.
084   */
085  public void initializeNotificationHandlers()
086         throws ConfigException, InitializationException
087  {
088    RootCfg rootConfiguration = serverContext.getRootConfig();
089    rootConfiguration.addAccountStatusNotificationHandlerAddListener (this);
090    rootConfiguration.addAccountStatusNotificationHandlerDeleteListener (this);
091
092    // Initialize existing account status notification handlers.
093    for (String handlerName:
094         rootConfiguration.listAccountStatusNotificationHandlers())
095    {
096      // Get the account status notification handler's configuration.
097      AccountStatusNotificationHandlerCfg config =
098        rootConfiguration.getAccountStatusNotificationHandler (handlerName);
099
100      // Register as a change listener for this notification handler
101      // entry so that we will be notified of any changes that may be
102      // made to it.
103      config.addChangeListener (this);
104
105      // Ignore this notification handler if it is disabled.
106      if (config.isEnabled())
107      {
108        // Load the notification handler implementation class.
109        String className = config.getJavaClass();
110        loadAndInstallNotificationHandler (className, config);
111      }
112    }
113  }
114
115  @Override
116  public boolean isConfigurationChangeAcceptable(
117      AccountStatusNotificationHandlerCfg configuration,
118      List<LocalizableMessage> unacceptableReasons
119      )
120  {
121    // returned status -- all is fine by default
122    boolean status = true;
123
124    if (configuration.isEnabled())
125    {
126      // Get the name of the class and make sure we can instantiate it as an
127      // entry cache.
128      String className = configuration.getJavaClass();
129      try
130      {
131        // Load the class but don't initialize it.
132        loadNotificationHandler(className, configuration, false);
133      }
134      catch (InitializationException ie)
135      {
136        unacceptableReasons.add(ie.getMessageObject());
137        status = false;
138      }
139    }
140
141    return status;
142  }
143
144  @Override
145  public ConfigChangeResult applyConfigurationChange(
146      AccountStatusNotificationHandlerCfg configuration
147      )
148  {
149    final ConfigChangeResult changeResult = new ConfigChangeResult();
150
151    // Get the configuration entry DN and the associated handler class.
152    DN configEntryDN = configuration.dn();
153    AccountStatusNotificationHandler<?> handler = notificationHandlers.get(configEntryDN);
154
155    // If the new configuration has the notification handler disabled,
156    // then remove it from the mapping list and clean it.
157    if (! configuration.isEnabled())
158    {
159      if (handler != null)
160      {
161        uninstallNotificationHandler (configEntryDN);
162      }
163
164      return changeResult;
165    }
166
167    // At this point, new configuration is enabled...
168    // If the current notification handler is already enabled then we
169    // don't do anything unless the class has changed in which case we
170    // should indicate that administrative action is required.
171    String newClassName = configuration.getJavaClass();
172    if (handler != null)
173    {
174      String curClassName = handler.getClass().getName();
175      boolean classIsNew = !newClassName.equals(curClassName);
176      if (classIsNew)
177      {
178        changeResult.setAdminActionRequired (true);
179      }
180      return changeResult;
181    }
182
183    // New entry cache is enabled and there were no previous one.
184    // Instantiate the new class and initialize it.
185    try
186    {
187      loadAndInstallNotificationHandler (newClassName, configuration);
188    }
189    catch (InitializationException ie)
190    {
191      changeResult.addMessage (ie.getMessageObject());
192      changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
193      return changeResult;
194    }
195
196    return changeResult;
197  }
198
199  @Override
200  public boolean isConfigurationAddAcceptable(
201      AccountStatusNotificationHandlerCfg configuration,
202      List<LocalizableMessage> unacceptableReasons
203      )
204  {
205    // returned status -- all is fine by default
206    boolean status = true;
207
208    // Make sure that no entry already exists with the specified DN.
209    DN configEntryDN = configuration.dn();
210    if (notificationHandlers.containsKey(configEntryDN))
211    {
212      unacceptableReasons.add(ERR_CONFIG_ACCTNOTHANDLER_EXISTS.get(configEntryDN));
213      status = false;
214    }
215    // If configuration is enabled then check that notification class
216    // can be instantiated.
217    else if (configuration.isEnabled())
218    {
219      // Get the name of the class and make sure we can instantiate it as
220      // an entry cache.
221      String className = configuration.getJavaClass();
222      try
223      {
224        // Load the class but don't initialize it.
225        loadNotificationHandler (className, configuration, false);
226      }
227      catch (InitializationException ie)
228      {
229        unacceptableReasons.add (ie.getMessageObject());
230        status = false;
231      }
232    }
233
234    return status;
235  }
236
237  @Override
238  public ConfigChangeResult applyConfigurationAdd(
239      AccountStatusNotificationHandlerCfg configuration
240      )
241  {
242    final ConfigChangeResult changeResult = new ConfigChangeResult();
243
244    // Register a change listener with it so we can be notified of changes
245    // to it over time.
246    configuration.addChangeListener(this);
247
248    if (configuration.isEnabled())
249    {
250      // Instantiate the class as an entry cache and initialize it.
251      String className = configuration.getJavaClass();
252      try
253      {
254        loadAndInstallNotificationHandler (className, configuration);
255      }
256      catch (InitializationException ie)
257      {
258        changeResult.addMessage (ie.getMessageObject());
259        changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
260        return changeResult;
261      }
262    }
263
264    return changeResult;
265  }
266
267  @Override
268  public boolean isConfigurationDeleteAcceptable(
269      AccountStatusNotificationHandlerCfg configuration,
270      List<LocalizableMessage> unacceptableReasons
271      )
272  {
273    // A delete should always be acceptable, so just return true.
274    return true;
275  }
276
277  @Override
278  public ConfigChangeResult applyConfigurationDelete(
279      AccountStatusNotificationHandlerCfg configuration
280      )
281  {
282    uninstallNotificationHandler (configuration.dn());
283    return new ConfigChangeResult();
284  }
285
286  /**
287   * Loads the specified class, instantiates it as a notification handler,
288   * and optionally initializes that instance. Any initialized notification
289   * handler is registered in the server.
290   *
291   * @param  className      The fully-qualified name of the notification handler
292   *                        class to load, instantiate, and initialize.
293   * @param  configuration  The configuration to use to initialize the
294   *                        notification handler, or {@code null} if the
295   *                        notification handler should not be initialized.
296   *
297   * @throws  InitializationException  If a problem occurred while attempting
298   *                                   to initialize the notification handler.
299   */
300  private void loadAndInstallNotificationHandler(
301       String className,
302       AccountStatusNotificationHandlerCfg configuration
303       )
304       throws InitializationException
305  {
306    // Load the notification handler class...
307    AccountStatusNotificationHandler
308        <? extends AccountStatusNotificationHandlerCfg> handlerClass;
309    handlerClass = loadNotificationHandler (className, configuration, true);
310
311    // ... and install the entry cache in the server.
312    DN configEntryDN = configuration.dn();
313    notificationHandlers.put (configEntryDN, handlerClass);
314    DirectoryServer.registerAccountStatusNotificationHandler(
315        configEntryDN,
316        handlerClass
317        );
318  }
319
320  /**
321   * Loads the specified class, instantiates it as a notification handler,
322   * and optionally initializes that instance.
323   *
324   * @param  className      The fully-qualified name of the notification handler
325   *                        class to load, instantiate, and initialize.
326   * @param  configuration  The configuration to use to initialize the
327   *                        notification handler.  It must not be {@code null}.
328   * @param  initialize     Indicates whether the account status notification
329   *                        handler instance should be initialized.
330   *
331   * @return  The possibly initialized notification handler.
332   *
333   * @throws  InitializationException  If a problem occurred while attempting
334   *                                   to initialize the notification handler.
335   */
336  private <T extends AccountStatusNotificationHandlerCfg>
337      AccountStatusNotificationHandler<T> loadNotificationHandler(
338      String className, T configuration, boolean initialize)
339      throws InitializationException
340  {
341    try
342    {
343      final AccountStatusNotificationHandlerCfgDefn definition =
344          AccountStatusNotificationHandlerCfgDefn.getInstance();
345      final ClassPropertyDefinition propertyDefinition =
346          definition.getJavaClassPropertyDefinition();
347      final Class<? extends AccountStatusNotificationHandler> handlerClass =
348          propertyDefinition.loadClass(className,
349              AccountStatusNotificationHandler.class);
350      final AccountStatusNotificationHandler<T> notificationHandler =
351          handlerClass.newInstance();
352
353      if (initialize)
354      {
355        notificationHandler.initializeStatusNotificationHandler(configuration);
356      }
357      else
358      {
359        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
360        if (!notificationHandler.isConfigurationAcceptable(configuration,
361            unacceptableReasons))
362        {
363          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
364          throw new InitializationException(
365              ERR_CONFIG_ACCTNOTHANDLER_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons));
366        }
367      }
368
369      return notificationHandler;
370    }
371    catch (Exception e)
372    {
373      LocalizableMessage message = ERR_CONFIG_ACCTNOTHANDLER_INITIALIZATION_FAILED.get(
374              className, configuration.dn(), stackTraceToSingleLineString(e));
375      throw new InitializationException(message, e);
376    }
377  }
378
379  /**
380   * Remove a notification handler that has been installed in the server.
381   *
382   * @param configEntryDN  the DN of the configuration entry associated to
383   *                       the notification handler to remove
384   */
385  private void uninstallNotificationHandler(DN configEntryDN)
386  {
387    AccountStatusNotificationHandler<?> handler = notificationHandlers.remove(configEntryDN);
388    if (handler != null)
389    {
390      DirectoryServer.deregisterAccountStatusNotificationHandler(configEntryDN);
391      handler.finalizeStatusNotificationHandler();
392    }
393  }
394}