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