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.List;
033import java.util.concurrent.ConcurrentHashMap;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.i18n.slf4j.LocalizedLogger;
037import org.forgerock.opendj.config.server.ConfigException;
038import org.opends.server.admin.ClassPropertyDefinition;
039import org.opends.server.admin.server.ConfigurationAddListener;
040import org.opends.server.admin.server.ConfigurationChangeListener;
041import org.opends.server.admin.server.ConfigurationDeleteListener;
042import org.opends.server.admin.server.ServerManagementContext;
043import org.opends.server.admin.std.meta.SynchronizationProviderCfgDefn;
044import org.opends.server.admin.std.server.RootCfg;
045import org.opends.server.admin.std.server.SynchronizationProviderCfg;
046import org.opends.server.api.SynchronizationProvider;
047import org.forgerock.opendj.config.server.ConfigChangeResult;
048import org.opends.server.types.DN;
049import org.opends.server.types.InitializationException;
050
051/**
052 * This class defines a utility that will be used to manage the configuration
053 * for the set of synchronization providers configured in the Directory Server.
054 * It will perform the necessary initialization of those synchronization
055 * providers when the server is first started, and then will manage any changes
056 * to them while the server is running.
057 */
058public class SynchronizationProviderConfigManager
059       implements ConfigurationChangeListener<SynchronizationProviderCfg>,
060       ConfigurationAddListener<SynchronizationProviderCfg>,
061       ConfigurationDeleteListener<SynchronizationProviderCfg>
062{
063  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
064
065  /**
066   * The mapping between configuration entry DNs and their corresponding
067   * synchronization provider implementations.
068   */
069  private final ConcurrentHashMap<DN,SynchronizationProvider<SynchronizationProviderCfg>> registeredProviders;
070
071  private final ServerContext serverContext;
072
073  /**
074   * Creates a new instance of this synchronization provider config manager.
075   *
076   * @param serverContext
077   *            The server context.
078   */
079  public SynchronizationProviderConfigManager(ServerContext serverContext)
080  {
081    this.serverContext = serverContext;
082    registeredProviders = new ConcurrentHashMap<>();
083  }
084
085  /**
086   * Initializes the configuration associated with the Directory Server
087   * synchronization providers.  This should only be called at Directory Server
088   * startup.
089   *
090   * @throws  ConfigException  If a critical configuration problem prevents any
091   *                           of the synchronization providers from starting
092   *                           properly.
093   *
094   * @throws  InitializationException  If a problem occurs while initializing
095   *                                   any of the synchronization providers that
096   *                                   is not related to the Directory Server
097   *                                   configuration.
098   */
099  public void initializeSynchronizationProviders()
100         throws ConfigException, InitializationException
101  {
102    // Create an internal server management context and retrieve
103    // the root configuration which has the synchronization provider relation.
104    ServerManagementContext context = ServerManagementContext.getInstance();
105    RootCfg root = context.getRootConfiguration();
106
107    // Register as an add and delete listener so that we can
108    // be notified when new synchronization providers are added or existing
109    // synchronization providers are removed.
110    root.addSynchronizationProviderAddListener(this);
111    root.addSynchronizationProviderDeleteListener(this);
112
113    // Initialize existing synchronization providers.
114    for (String name : root.listSynchronizationProviders())
115    {
116      // Get the synchronization provider's configuration.
117      // This will automatically decode and validate its properties.
118      SynchronizationProviderCfg config = root.getSynchronizationProvider(name);
119
120      // Register as a change listener for this synchronization provider
121      // entry so that we can be notified when it is disabled or enabled.
122      config.addChangeListener(this);
123
124      // Ignore this synchronization provider if it is disabled.
125      if (config.isEnabled())
126      {
127        // Perform initialization, load the synchronization provider's
128        // implementation class and initialize it.
129        SynchronizationProvider<SynchronizationProviderCfg> provider =
130          getSynchronizationProvider(config);
131
132        // Register the synchronization provider with the Directory Server.
133        DirectoryServer.registerSynchronizationProvider(provider);
134
135        // Put this synchronization provider in the hash map so that we will be
136        // able to find it if it is deleted or disabled.
137        registeredProviders.put(config.dn(), provider);
138      }
139    }
140  }
141
142
143
144  /** {@inheritDoc} */
145  @Override
146  public ConfigChangeResult applyConfigurationChange(
147      SynchronizationProviderCfg configuration)
148  {
149    final ConfigChangeResult ccr = new ConfigChangeResult();
150
151    // Attempt to get the existing synchronization provider. This will only
152    // succeed if it is currently enabled.
153    DN dn = configuration.dn();
154    SynchronizationProvider<SynchronizationProviderCfg> provider =
155      registeredProviders.get(dn);
156
157    // See whether the synchronization provider should be enabled.
158    if (provider == null)
159    {
160      if (configuration.isEnabled())
161      {
162        // The synchronization provider needs to be enabled. Load, initialize,
163        // and register the synchronization provider as per the add listener
164        // method.
165        try
166        {
167          // Perform initialization, load the synchronization provider's
168          // implementation class and initialize it.
169          provider = getSynchronizationProvider(configuration);
170
171          // Register the synchronization provider with the Directory Server.
172          DirectoryServer.registerSynchronizationProvider(provider);
173
174          // Put this synchronization provider in the hash map so that we will
175          // be able to find it if it is deleted or disabled.
176          registeredProviders.put(configuration.dn(), provider);
177        }
178        catch (ConfigException e)
179        {
180          if (logger.isTraceEnabled())
181          {
182            logger.traceException(e);
183            ccr.addMessage(e.getMessageObject());
184            ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
185          }
186        }
187        catch (Exception e)
188        {
189          logger.traceException(e);
190
191          ccr.addMessage(ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(configuration.dn(),
192              stackTraceToSingleLineString(e)));
193          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
194        }
195      }
196    }
197    else
198    {
199      if (configuration.isEnabled())
200      {
201        // The synchronization provider is currently active, so we don't
202        // need to do anything. Changes to the class name cannot be
203        // applied dynamically, so if the class name did change then
204        // indicate that administrative action is required for that
205        // change to take effect.
206        String className = configuration.getJavaClass();
207        if (!className.equals(provider.getClass().getName()))
208        {
209          ccr.setAdminActionRequired(true);
210        }
211      }
212      else
213      {
214        // The connection handler is being disabled so remove it from
215        // the DirectorySerevr list, shut it down and  remove it from the
216        // hash map.
217        DirectoryServer.deregisterSynchronizationProvider(provider);
218        provider.finalizeSynchronizationProvider();
219        registeredProviders.remove(dn);
220      }
221    }
222    return ccr;
223  }
224
225
226
227  /** {@inheritDoc} */
228  @Override
229  public boolean isConfigurationChangeAcceptable(
230      SynchronizationProviderCfg configuration,
231      List<LocalizableMessage> unacceptableReasons)
232  {
233    return !configuration.isEnabled()
234        || isJavaClassAcceptable(configuration, unacceptableReasons);
235  }
236
237
238
239  /** {@inheritDoc} */
240  @Override
241  public ConfigChangeResult applyConfigurationAdd(
242    SynchronizationProviderCfg configuration)
243  {
244    final ConfigChangeResult ccr = new ConfigChangeResult();
245
246    // Register as a change listener for this synchronization provider entry
247    // so that we will be notified if when it is disabled or enabled.
248    configuration.addChangeListener(this);
249
250    // Ignore this synchronization provider if it is disabled.
251    if (configuration.isEnabled())
252    {
253      try
254      {
255        // Perform initialization, load the synchronization provider's
256        // implementation class and initialize it.
257        SynchronizationProvider<SynchronizationProviderCfg> provider =
258          getSynchronizationProvider(configuration);
259
260        // Register the synchronization provider with the Directory Server.
261        DirectoryServer.registerSynchronizationProvider(provider);
262
263        // Put this synchronization provider in the hash map so that we will be
264        // able to find it if it is deleted or disabled.
265        registeredProviders.put(configuration.dn(), provider);
266      }
267      catch (ConfigException e)
268      {
269        if (logger.isTraceEnabled())
270        {
271          logger.traceException(e);
272          ccr.addMessage(e.getMessageObject());
273          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
274        }
275      }
276      catch (Exception e)
277      {
278        logger.traceException(e);
279
280        ccr.addMessage(ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(configuration.dn(),
281            stackTraceToSingleLineString(e)));
282        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
283      }
284    }
285
286    return ccr;
287  }
288
289
290
291  /** {@inheritDoc} */
292  @Override
293  public boolean isConfigurationAddAcceptable(
294      SynchronizationProviderCfg configuration,
295      List<LocalizableMessage> unacceptableReasons)
296  {
297    return !configuration.isEnabled()
298        || isJavaClassAcceptable(configuration, unacceptableReasons);
299  }
300
301
302
303  /**
304   * Check if the class provided in the configuration is an acceptable
305   * java class for a synchronization provider.
306   *
307   * @param configuration       The configuration for which the class must be
308   *                            checked.
309   * @return                    true if the class is acceptable or false if not.
310   */
311  @SuppressWarnings("unchecked")
312  private SynchronizationProvider<SynchronizationProviderCfg>
313    getSynchronizationProvider(SynchronizationProviderCfg configuration)
314    throws ConfigException
315  {
316    String className = configuration.getJavaClass();
317    SynchronizationProviderCfgDefn d =
318      SynchronizationProviderCfgDefn.getInstance();
319    ClassPropertyDefinition pd =
320      d.getJavaClassPropertyDefinition();
321
322    // Load the class
323    Class<? extends SynchronizationProvider> theClass;
324    SynchronizationProvider<SynchronizationProviderCfg> provider;
325    try
326    {
327       theClass = pd.loadClass(className, SynchronizationProvider.class);
328    } catch (Exception e)
329    {
330       // Handle the exception: put a message in the unacceptable reasons.
331       LocalizableMessage message = ERR_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS.
332           get(className, configuration.dn(), stackTraceToSingleLineString(e));
333       throw new ConfigException(message, e);
334    }
335    try
336    {
337      // Instantiate the class.
338      provider = theClass.newInstance();
339    } catch (Exception e)
340    {
341      // Handle the exception: put a message in the unacceptable reasons.
342      LocalizableMessage message = ERR_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER.
343          get(className, configuration.dn(), stackTraceToSingleLineString(e));
344      throw new ConfigException(message, e);
345    }
346    try
347    {
348      // Initialize the Synchronization Provider.
349      provider.initializeSynchronizationProvider(configuration);
350    } catch (Exception e)
351    {
352      try
353      {
354        provider.finalizeSynchronizationProvider();
355      }
356      catch(Exception ce)
357      {}
358
359      // Handle the exception: put a message in the unacceptable reasons.
360      throw new ConfigException(
361          ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(configuration.dn(), stackTraceToSingleLineString(e)), e);
362    }
363    return provider;
364  }
365
366  /**
367   * Check if the class provided in the configuration is an acceptable
368   * java class for a synchronization provider.
369   *
370   * @param configuration       The configuration for which the class must be
371   *                            checked.
372   * @param unacceptableReasons A list containing the reasons why the class is
373   *                            not acceptable.
374   *
375   * @return                    true if the class is acceptable or false if not.
376   */
377  private boolean isJavaClassAcceptable(
378      SynchronizationProviderCfg configuration,
379      List<LocalizableMessage> unacceptableReasons)
380  {
381    String className = configuration.getJavaClass();
382    SynchronizationProviderCfgDefn d =
383      SynchronizationProviderCfgDefn.getInstance();
384    ClassPropertyDefinition pd = d.getJavaClassPropertyDefinition();
385
386    try
387    {
388      Class<? extends SynchronizationProvider> theClass =
389          pd.loadClass(className, SynchronizationProvider.class);
390      SynchronizationProvider provider = theClass.newInstance();
391
392      return provider.isConfigurationAcceptable(configuration,
393          unacceptableReasons);
394    } catch (Exception e)
395    {
396      // Handle the exception: put a message in the unacceptable reasons.
397      LocalizableMessage message = ERR_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER.get(
398          className, configuration.dn(), stackTraceToSingleLineString(e));
399      unacceptableReasons.add(message);
400      return false;
401    }
402  }
403
404  /** {@inheritDoc} */
405  @Override
406  public ConfigChangeResult applyConfigurationDelete(
407      SynchronizationProviderCfg configuration)
408  {
409    final ConfigChangeResult ccr = new ConfigChangeResult();
410
411    // See if the entry is registered as a synchronization provider. If so,
412    // deregister and stop it.
413    DN dn = configuration.dn();
414    SynchronizationProvider provider = registeredProviders.get(dn);
415    if (provider != null)
416    {
417      DirectoryServer.deregisterSynchronizationProvider(provider);
418      provider.finalizeSynchronizationProvider();
419    }
420    return ccr;
421  }
422
423
424
425  /** {@inheritDoc} */
426  @Override
427  public boolean isConfigurationDeleteAcceptable(
428      SynchronizationProviderCfg configuration,
429      List<LocalizableMessage> unacceptableReasons)
430  {
431    // A delete should always be acceptable, so just return true.
432    return true;
433  }
434}