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