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