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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    
030    
031    
032    import static org.opends.server.loggers.debug.DebugLogger.*;
033    import org.opends.server.loggers.debug.DebugTracer;
034    import static org.opends.messages.ConfigMessages.*;
035    
036    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
037    
038    import java.lang.reflect.Method;
039    import java.util.ArrayList;
040    import java.util.List;
041    import java.util.concurrent.ConcurrentHashMap;
042    
043    import org.opends.server.admin.ClassPropertyDefinition;
044    import org.opends.server.admin.server.ConfigurationAddListener;
045    import org.opends.server.admin.server.ConfigurationChangeListener;
046    import org.opends.server.admin.server.ConfigurationDeleteListener;
047    import org.opends.server.admin.server.ServerManagementContext;
048    import org.opends.server.admin.std.meta.SynchronizationProviderCfgDefn;
049    import org.opends.server.admin.std.server.RootCfg;
050    import org.opends.server.admin.std.server.SynchronizationProviderCfg;
051    import org.opends.server.api.SynchronizationProvider;
052    import org.opends.server.config.ConfigException;
053    import org.opends.server.types.ConfigChangeResult;
054    import org.opends.server.types.DN;
055    import org.opends.server.types.DebugLogLevel;
056    import org.opends.server.types.InitializationException;
057    import org.opends.server.types.ResultCode;
058    
059    
060    
061    /**
062     * This class defines a utility that will be used to manage the configuration
063     * for the set of synchronization providers configured in the Directory Server.
064     * It will perform the necessary initialization of those synchronization
065     * providers when the server is first started, and then will manage any changes
066     * to them while the server is running.
067     */
068    public class SynchronizationProviderConfigManager
069           implements ConfigurationChangeListener<SynchronizationProviderCfg>,
070           ConfigurationAddListener<SynchronizationProviderCfg>,
071           ConfigurationDeleteListener<SynchronizationProviderCfg>
072    {
073      /**
074       * The tracer object for the debug logger.
075       */
076      private static final DebugTracer TRACER = getTracer();
077    
078    
079    
080    
081      // The mapping between configuration entry DNs and their corresponding
082      // synchronization provider implementations.
083      private ConcurrentHashMap<DN,
084        SynchronizationProvider<SynchronizationProviderCfg>> registeredProviders =
085          new ConcurrentHashMap<DN,
086            SynchronizationProvider<SynchronizationProviderCfg>>();
087    
088    
089    
090    
091      /**
092       * Creates a new instance of this synchronization provider config manager.
093       */
094      public SynchronizationProviderConfigManager()
095      {
096        // No implementation is required.
097      }
098    
099    
100    
101      /**
102       * Initializes the configuration associated with the Directory Server
103       * synchronization providers.  This should only be called at Directory Server
104       * startup.
105       *
106       * @throws  ConfigException  If a critical configuration problem prevents any
107       *                           of the synchronization providers from starting
108       *                           properly.
109       *
110       * @throws  InitializationException  If a problem occurs while initializing
111       *                                   any of the synchronization providers that
112       *                                   is not related to the Directory Server
113       *                                   configuration.
114       */
115      public void initializeSynchronizationProviders()
116             throws ConfigException, InitializationException
117      {
118        // Create an internal server management context and retrieve
119        // the root configuration which has the synchronization provider relation.
120        ServerManagementContext context = ServerManagementContext.getInstance();
121        RootCfg root = context.getRootConfiguration();
122    
123        // Register as an add and delete listener so that we can
124        // be notified when new synchronization providers are added or existing
125        // sycnhronization providers are removed.
126        root.addSynchronizationProviderAddListener(this);
127        root.addSynchronizationProviderDeleteListener(this);
128    
129        // Initialize existing synchronization providers.
130        for (String name : root.listSynchronizationProviders())
131        {
132          // Get the synchronization provider's configuration.
133          // This will automatically decode and validate its properties.
134          SynchronizationProviderCfg config = root.getSynchronizationProvider(name);
135    
136          // Register as a change listener for this synchronization provider
137          // entry so that we can be notified when it is disabled or enabled.
138          config.addChangeListener(this);
139    
140          // Ignore this synchronization provider if it is disabled.
141          if (config.isEnabled())
142          {
143            // Perform initialization, load the synchronization provider's
144            // implementation class and initialize it.
145            SynchronizationProvider<SynchronizationProviderCfg> provider =
146              getSynchronizationProvider(config);
147    
148            // Register the synchronization provider with the Directory Server.
149            DirectoryServer.registerSynchronizationProvider(provider);
150    
151            // Put this synchronization provider in the hash map so that we will be
152            // able to find it if it is deleted or disabled.
153            registeredProviders.put(config.dn(), provider);
154          }
155        }
156      }
157    
158    
159    
160      /**
161       * {@inheritDoc}
162       */
163      public ConfigChangeResult applyConfigurationChange(
164          SynchronizationProviderCfg configuration)
165      {
166        // Default result code.
167        ResultCode resultCode = ResultCode.SUCCESS;
168        boolean adminActionRequired = false;
169        ArrayList<Message> messages = new ArrayList<Message>();
170    
171        // Attempt to get the existing synchronization provider. This will only
172        // succeed if it is currently enabled.
173        DN dn = configuration.dn();
174        SynchronizationProvider<SynchronizationProviderCfg> provider =
175          registeredProviders.get(dn);
176    
177        // See whether the synchronization provider should be enabled.
178        if (provider == null)
179        {
180          if (configuration.isEnabled())
181          {
182            // The synchronization provider needs to be enabled. Load, initialize,
183            // and register the synchronization provider as per the add listener
184            // method.
185            try
186            {
187              // Perform initialization, load the synchronization provider's
188              // implementation class and initialize it.
189              provider = getSynchronizationProvider(configuration);
190    
191              // Register the synchronization provider with the Directory Server.
192              DirectoryServer.registerSynchronizationProvider(provider);
193    
194              // Put this synchronization provider in the hash map so that we will
195              // be able to find it if it is deleted or disabled.
196              registeredProviders.put(configuration.dn(), provider);
197            }
198            catch (ConfigException e)
199            {
200              if (debugEnabled())
201              {
202                TRACER.debugCaught(DebugLogLevel.ERROR, e);
203                messages.add(e.getMessageObject());
204                resultCode = DirectoryServer.getServerErrorResultCode();
205              }
206            }
207            catch (Exception e)
208            {
209              if (debugEnabled())
210              {
211                TRACER.debugCaught(DebugLogLevel.ERROR, e);
212              }
213    
214              messages.add(ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(
215                      String.valueOf(configuration.getJavaClass()),
216                      String.valueOf(configuration.dn())));
217              resultCode = DirectoryServer.getServerErrorResultCode();
218            }
219          }
220        }
221        else
222        {
223          if (configuration.isEnabled())
224          {
225            // The synchronization provider is currently active, so we don't
226            // need to do anything. Changes to the class name cannot be
227            // applied dynamically, so if the class name did change then
228            // indicate that administrative action is required for that
229            // change to take effect.
230            String className = configuration.getJavaClass();
231            if (!className.equals(provider.getClass().getName()))
232            {
233              adminActionRequired = true;
234            }
235          }
236          else
237          {
238            // The connection handler is being disabled so remove it from
239            // the DirectorySerevr list, shut it down and  remove it from the
240            // hash map.
241            DirectoryServer.deregisterSynchronizationProvider(provider);
242            provider.finalizeSynchronizationProvider();
243            registeredProviders.remove(dn);
244          }
245        }
246        // Return the configuration result.
247        return new ConfigChangeResult(resultCode, adminActionRequired,
248            messages);
249      }
250    
251    
252    
253      /**
254       * {@inheritDoc}
255       */
256      public boolean isConfigurationChangeAcceptable(
257          SynchronizationProviderCfg configuration,
258          List<Message> unacceptableReasons)
259      {
260        if (configuration.isEnabled())
261        {
262          // It's enabled so always validate the class.
263          return isJavaClassAcceptable(configuration, unacceptableReasons);
264        } else
265        {
266          // It's disabled so ignore it.
267          return true;
268        }
269      }
270    
271    
272    
273      /**
274       * {@inheritDoc}
275       */
276      public ConfigChangeResult applyConfigurationAdd(
277        SynchronizationProviderCfg configuration)
278      {
279        // Default result code.
280        ResultCode resultCode = ResultCode.SUCCESS;
281        boolean adminActionRequired = false;
282        ArrayList<Message> messages = new ArrayList<Message>();
283    
284        // Register as a change listener for this synchronization provider entry
285        // so that we will be notified if when it is disabled or enabled.
286        configuration.addChangeListener(this);
287    
288        // Ignore this synchronization provider if it is disabled.
289        if (configuration.isEnabled())
290        {
291          try
292          {
293            // Perform initialization, load the synchronization provider's
294            // implementation class and initialize it.
295            SynchronizationProvider<SynchronizationProviderCfg> provider =
296              getSynchronizationProvider(configuration);
297    
298            // Register the synchronization provider with the Directory Server.
299            DirectoryServer.registerSynchronizationProvider(provider);
300    
301            // Put this synchronization provider in the hash map so that we will be
302            // able to find it if it is deleted or disabled.
303            registeredProviders.put(configuration.dn(), provider);
304          }
305          catch (ConfigException e)
306          {
307            if (debugEnabled())
308            {
309              TRACER.debugCaught(DebugLogLevel.ERROR, e);
310              messages.add(e.getMessageObject());
311              resultCode = DirectoryServer.getServerErrorResultCode();
312            }
313          }
314          catch (Exception e)
315          {
316            if (debugEnabled())
317            {
318              TRACER.debugCaught(DebugLogLevel.ERROR, e);
319            }
320    
321            messages.add(ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(
322                    String.valueOf(configuration.getJavaClass()),
323                               String.valueOf(configuration.dn())));
324            resultCode = DirectoryServer.getServerErrorResultCode();
325          }
326        }
327    
328        // Return the configuration result.
329        return new ConfigChangeResult(resultCode, adminActionRequired,
330            messages);
331      }
332    
333    
334    
335      /**
336       * {@inheritDoc}
337       */
338      public boolean isConfigurationAddAcceptable(
339          SynchronizationProviderCfg configuration,
340          List<Message> unacceptableReasons)
341      {
342        if (configuration.isEnabled())
343        {
344          // It's enabled so always validate the class.
345          return isJavaClassAcceptable(configuration, unacceptableReasons);
346        } else
347        {
348          // It's disabled so ignore it.
349          return true;
350        }
351      }
352    
353    
354    
355      /**
356       * Check if the class provided in the configuration is an acceptable
357       * java class for a synchronization provider.
358       *
359       * @param configuration       The configuration for which the class must be
360       *                            checked.
361       * @return                    true if the class is acceptable or false if not.
362       */
363      @SuppressWarnings("unchecked")
364      private SynchronizationProvider<SynchronizationProviderCfg>
365        getSynchronizationProvider(SynchronizationProviderCfg configuration)
366        throws ConfigException
367      {
368        String className = configuration.getJavaClass();
369        SynchronizationProviderCfgDefn d =
370          SynchronizationProviderCfgDefn.getInstance();
371        ClassPropertyDefinition pd =
372          d.getJavaClassPropertyDefinition();
373    
374        // Load the class
375        Class<? extends SynchronizationProvider> theClass;
376        SynchronizationProvider<SynchronizationProviderCfg> provider;
377        try
378        {
379           theClass = pd.loadClass(className, SynchronizationProvider.class);
380        } catch (Exception e)
381        {
382           // Handle the exception: put a message in the unacceptable reasons.
383           Message message = ERR_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS.
384               get(String.valueOf(className), String.valueOf(configuration.dn()),
385                   stackTraceToSingleLineString(e));
386           throw new ConfigException(message, e);
387        }
388        try
389        {
390          // Instantiate the class.
391          provider = theClass.newInstance();
392        } catch (Exception e)
393        {
394          // Handle the exception: put a message in the unacceptable reasons.
395          Message message = ERR_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER.
396              get(String.valueOf(className), String.valueOf(configuration.dn()),
397                  stackTraceToSingleLineString(e));
398          throw new ConfigException(message, e);
399        }
400        try
401        {
402          // Initialize the Synchronization Provider.
403          provider.initializeSynchronizationProvider(configuration);
404        } catch (Exception e)
405        {
406          // Handle the exception: put a message in the unacceptable reasons.
407          Message message = ERR_CONFIG_SYNCH_ERROR_INITIALIZING_PROVIDER.get(
408                  String.valueOf(className), String.valueOf(configuration.dn()));
409          throw new ConfigException(message, e);
410        }
411        return provider;
412      }
413    
414      /**
415       * Check if the class provided in the configuration is an acceptable
416       * java class for a synchronization provider.
417       *
418       * @param configuration       The configuration for which the class must be
419       *                            checked.
420       * @param unacceptableReasons A list containing the reasons why the class is
421       *                            not acceptable.
422       *
423       * @return                    true if the class is acceptable or false if not.
424       */
425      private boolean isJavaClassAcceptable(
426          SynchronizationProviderCfg configuration,
427          List<Message> unacceptableReasons)
428      {
429        String className = configuration.getJavaClass();
430        SynchronizationProviderCfgDefn d =
431          SynchronizationProviderCfgDefn.getInstance();
432        ClassPropertyDefinition pd =
433          d.getJavaClassPropertyDefinition();
434    
435        // Load the class and cast it to a synchronizationProvider.
436        SynchronizationProvider provider = null;
437        Class<? extends SynchronizationProvider> theClass;
438        try
439        {
440           theClass = pd.loadClass(className, SynchronizationProvider.class);
441           provider = theClass.newInstance();
442        } catch (Exception e)
443        {
444           // Handle the exception: put a message in the unacceptable reasons.
445           Message message = ERR_CONFIG_SYNCH_UNABLE_TO_LOAD_PROVIDER_CLASS.get(
446                   String.valueOf(className),
447                   String.valueOf(configuration.dn()),
448                   stackTraceToSingleLineString(e));
449           unacceptableReasons.add(message);
450           return false;
451        }
452        // Check that the implementation class implements the correct interface.
453        try
454        {
455          // Determine the initialization method to use: it must take a
456          // single parameter which is the exact type of the configuration
457          // object.
458          Method method = theClass.getMethod("isConfigurationAcceptable",
459                                             SynchronizationProviderCfg.class,
460                                             List.class);
461          Boolean acceptable = (Boolean) method.invoke(provider, configuration,
462                                                       unacceptableReasons);
463    
464          if (! acceptable)
465          {
466            return false;
467          }
468        } catch (Exception e)
469        {
470          // Handle the exception: put a message in the unacceptable reasons.
471          Message message = ERR_CONFIG_SYNCH_UNABLE_TO_INSTANTIATE_PROVIDER.get(
472                  String.valueOf(className),
473                  String.valueOf(configuration.dn()),
474                  stackTraceToSingleLineString(e));
475          unacceptableReasons.add(message);
476          return false;
477        }
478    
479        // The class is valid as far as we can tell.
480        return true;
481      }
482    
483      /**
484       * {@inheritDoc}
485       */
486      public ConfigChangeResult applyConfigurationDelete(
487          SynchronizationProviderCfg configuration)
488      {
489        //  Default result code.
490        ResultCode resultCode = ResultCode.SUCCESS;
491        boolean adminActionRequired = false;
492    
493        // See if the entry is registered as a synchronization provider. If so,
494        // deregister and stop it.
495        DN dn = configuration.dn();
496        SynchronizationProvider provider = registeredProviders.get(dn);
497        if (provider != null)
498        {
499          DirectoryServer.deregisterSynchronizationProvider(provider);
500          provider.finalizeSynchronizationProvider();
501        }
502        return new ConfigChangeResult(resultCode, adminActionRequired);
503      }
504    
505    
506    
507      /**
508       * {@inheritDoc}
509       */
510      public boolean isConfigurationDeleteAcceptable(
511          SynchronizationProviderCfg configuration,
512          List<Message> unacceptableReasons)
513      {
514        // A delete should always be acceptable, so just return true.
515        return true;
516      }
517    }
518    
519    
520    
521