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